gstavimux.c 69.5 KB
Newer Older
1 2
/* AVI muxer plugin for GStreamer
 * Copyright (C) 2002 Ronald Bultje <rbultje@ronald.bitfreak.net>
3
 *           (C) 2006 Mark Nauwelaerts <manauw@skynet.be>
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

21 22 23 24 25 26 27
/* based on:
 * - the old avimuxer (by Wim Taymans)
 * - xawtv's aviwriter (by Gerd Knorr)
 * - mjpegtools' avilib (by Rainer Johanni)
 * - openDML large-AVI docs
 */

28 29 30 31
/**
 * SECTION:element-avimux
 *
 * Muxes raw or compressed audio and/or video streams into an AVI file.
32 33 34 35 36
 *
 * <refsect2>
 * <title>Example launch lines</title>
 * <para>(write everything in one line, without the backslash characters)</para>
 * |[
37
 * gst-launch videotestsrc num-buffers=250 \
38 39 40 41 42
 * ! 'video/x-raw-yuv,format=(fourcc)I420,width=320,height=240,framerate=(fraction)25/1' \
 * ! queue ! mux. \
 * audiotestsrc num-buffers=440 ! audioconvert \
 * ! 'audio/x-raw-int,rate=44100,channels=2' ! queue ! mux. \
 * avimux name=mux ! filesink location=test.avi
43
 * ]| This will create an .AVI file containing an uncompressed video stream
44 45
 * with a test picture and an uncompressed audio stream containing a 
 * test sound.
46
 * |[
47
 * gst-launch videotestsrc num-buffers=250 \
48 49 50 51 52
 * ! 'video/x-raw-yuv,format=(fourcc)I420,width=320,height=240,framerate=(fraction)25/1' \
 * ! xvidenc ! queue ! mux. \
 * audiotestsrc num-buffers=440 ! audioconvert ! 'audio/x-raw-int,rate=44100,channels=2' \
 * ! lame ! queue ! mux. \
 * avimux name=mux ! filesink location=test.avi
53
 * ]| This will create an .AVI file containing the same test video and sound
54 55 56 57
 * as above, only that both streams will be compressed this time. This will
 * only work if you have the necessary encoder elements installed of course.
 * </refsect2>
 */
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
58

59 60 61
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
62

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
63
#include "gst/gst-i18n-plugin.h"
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
64 65 66
#include <stdlib.h>
#include <string.h>

67 68
#include <gst/video/video.h>

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
69 70
#include "gstavimux.h"

71 72
GST_DEBUG_CATEGORY_STATIC (avimux_debug);
#define GST_CAT_DEFAULT avimux_debug
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
73

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
74 75
enum
{
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
76
  ARG_0,
77
  ARG_BIGFILE
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
78 79
};

80 81
#define DEFAULT_BIGFILE TRUE

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
82 83 84 85 86 87
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-msvideo")
    );

David Schleef's avatar
David Schleef committed
88
static GstStaticPadTemplate video_sink_factory =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
89 90 91 92
    GST_STATIC_PAD_TEMPLATE ("video_%d",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS ("video/x-raw-yuv, "
93 94
        "format = (fourcc) { YUY2, I420 }, "
        "width = (int) [ 16, 4096 ], "
Ronald S. Bultje's avatar
Ronald S. Bultje committed
95
        "height = (int) [ 16, 4096 ], "
96
        "framerate = (fraction) [ 0, MAX ]; "
97
        "image/jpeg, "
98
        "width = (int) [ 16, 4096 ], "
Ronald S. Bultje's avatar
Ronald S. Bultje committed
99
        "height = (int) [ 16, 4096 ], "
100
        "framerate = (fraction) [ 0, MAX ]; "
101 102 103
        "video/x-divx, "
        "width = (int) [ 16, 4096 ], "
        "height = (int) [ 16, 4096 ], "
104
        "framerate = (fraction) [ 0, MAX ], "
105 106 107
        "divxversion = (int) [ 3, 5 ]; "
        "video/x-xvid, "
        "width = (int) [ 16, 4096 ], "
Ronald S. Bultje's avatar
Ronald S. Bultje committed
108
        "height = (int) [ 16, 4096 ], "
109
        "framerate = (fraction) [ 0, MAX ]; "
110 111
        "video/x-3ivx, "
        "width = (int) [ 16, 4096 ], "
Ronald S. Bultje's avatar
Ronald S. Bultje committed
112
        "height = (int) [ 16, 4096 ], "
113
        "framerate = (fraction) [ 0, MAX ]; "
114 115 116
        "video/x-msmpeg, "
        "width = (int) [ 16, 4096 ], "
        "height = (int) [ 16, 4096 ], "
117
        "framerate = (fraction) [ 0, MAX ], "
118 119 120 121
        "msmpegversion = (int) [ 41, 43 ]; "
        "video/mpeg, "
        "width = (int) [ 16, 4096 ], "
        "height = (int) [ 16, 4096 ], "
122
        "framerate = (fraction) [ 0, MAX ], "
123
        "mpegversion = (int) { 1, 2, 4}, "
124 125 126
        "systemstream = (boolean) FALSE; "
        "video/x-h263, "
        "width = (int) [ 16, 4096 ], "
Ronald S. Bultje's avatar
Ronald S. Bultje committed
127
        "height = (int) [ 16, 4096 ], "
128
        "framerate = (fraction) [ 0, MAX ]; "
129 130 131
        "video/x-h264, "
        "width = (int) [ 16, 4096 ], "
        "height = (int) [ 16, 4096 ], "
132
        "framerate = (fraction) [ 0, MAX ]; "
133 134 135
        "video/x-dv, "
        "width = (int) 720, "
        "height = (int) { 576, 480 }, "
136
        "framerate = (fraction) [ 0, MAX ], "
137 138
        "systemstream = (boolean) FALSE; "
        "video/x-huffyuv, "
Ronald S. Bultje's avatar
Ronald S. Bultje committed
139
        "width = (int) [ 16, 4096 ], "
140 141 142
        "height = (int) [ 16, 4096 ], " "framerate = (fraction) [ 0, MAX ];"
        "video/x-dirac, "
        "width = (int) [ 16, 4096 ], "
Thiago Santos's avatar
Thiago Santos committed
143 144 145 146
        "height = (int) [ 16, 4096 ], " "framerate = (fraction) [ 0, MAX ];"
        "video/x-wmv, "
        "width = (int) [ 16, 4096 ], "
        "height = (int) [ 16, 4096 ], " "framerate = (fraction) [ 0, MAX ], "
147 148 149 150 151
        "wmvversion = (int) [ 1, 3];"
        "image/x-jpc, "
        "width = (int) [ 1, 2147483647 ], "
        "height = (int) [ 1, 2147483647 ], "
        "framerate = (fraction) [ 0, MAX ]")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
152 153
    );

David Schleef's avatar
David Schleef committed
154
static GstStaticPadTemplate audio_sink_factory =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
155 156 157 158
    GST_STATIC_PAD_TEMPLATE ("audio_%d",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS ("audio/x-raw-int, "
159 160 161 162 163 164 165 166 167
        "endianness = (int) LITTLE_ENDIAN, "
        "signed = (boolean) { TRUE, FALSE }, "
        "width = (int) { 8, 16 }, "
        "depth = (int) { 8, 16 }, "
        "rate = (int) [ 1000, 96000 ], "
        "channels = (int) [ 1, 2 ]; "
        "audio/mpeg, "
        "mpegversion = (int) 1, "
        "layer = (int) [ 1, 3 ], "
168
        "rate = (int) [ 1000, 96000 ], " "channels = (int) [ 1, 2 ]; "
169 170
        "audio/mpeg, "
        "mpegversion = (int) 4, "
171
        "stream-format = (string) raw, "
172
        "rate = (int) [ 1000, 96000 ], " "channels = (int) [ 1, 2 ]; "
173
/*#if 0 VC6 doesn't support #if here ...
174
        "audio/x-vorbis, "
175
        "rate = (int) [ 1000, 96000 ], " "channels = (int) [ 1, 2 ]; "
176
#endif*/
177
        "audio/x-ac3, "
178 179 180 181
        "rate = (int) [ 1000, 96000 ], " "channels = (int) [ 1, 2 ]; "
        "audio/x-alaw, "
        "rate = (int) [ 1000, 48000 ], " "channels = (int) [ 1, 2 ]; "
        "audio/x-mulaw, "
Thiago Santos's avatar
Thiago Santos committed
182 183 184 185
        "rate = (int) [ 1000, 48000 ], " "channels = (int) [ 1, 2 ]; "
        "audio/x-wma, "
        "rate = (int) [ 1000, 96000 ], " "channels = (int) [ 1, 2 ], "
        "wmaversion = (int) [ 1, 2 ] ")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
186 187
    );

188 189 190
static void gst_avi_mux_base_init (gpointer g_class);
static void gst_avi_mux_class_init (GstAviMuxClass * klass);
static void gst_avi_mux_init (GstAviMux * avimux);
191
static void gst_avi_mux_pad_reset (GstAviPad * avipad, gboolean free);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
192

193 194 195 196
static GstFlowReturn gst_avi_mux_collect_pads (GstCollectPads * pads,
    GstAviMux * avimux);
static gboolean gst_avi_mux_handle_event (GstPad * pad, GstEvent * event);
static GstPad *gst_avi_mux_request_new_pad (GstElement * element,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
197
    GstPadTemplate * templ, const gchar * name);
198 199
static void gst_avi_mux_release_pad (GstElement * element, GstPad * pad);
static void gst_avi_mux_set_property (GObject * object,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
200
    guint prop_id, const GValue * value, GParamSpec * pspec);
201
static void gst_avi_mux_get_property (GObject * object,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
202
    guint prop_id, GValue * value, GParamSpec * pspec);
203
static GstStateChangeReturn gst_avi_mux_change_state (GstElement * element,
204
    GstStateChange transition);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
205 206

static GstElementClass *parent_class = NULL;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
207

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
208
GType
209
gst_avi_mux_get_type (void)
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
210 211 212 213 214
{
  static GType avimux_type = 0;

  if (!avimux_type) {
    static const GTypeInfo avimux_info = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
215
      sizeof (GstAviMuxClass),
216
      gst_avi_mux_base_init,
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
217
      NULL,
218
      (GClassInitFunc) gst_avi_mux_class_init,
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
219 220
      NULL,
      NULL,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
221
      sizeof (GstAviMux),
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
222
      0,
223
      (GInstanceInitFunc) gst_avi_mux_init,
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
224
    };
225 226 227 228 229
    static const GInterfaceInfo tag_setter_info = {
      NULL,
      NULL,
      NULL
    };
230

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
231
    avimux_type =
232
        g_type_register_static (GST_TYPE_ELEMENT, "GstAviMux", &avimux_info, 0);
233 234
    g_type_add_interface_static (avimux_type, GST_TYPE_TAG_SETTER,
        &tag_setter_info);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
235 236 237 238
  }
  return avimux_type;
}

239
static void
240
gst_avi_mux_base_init (gpointer g_class)
241 242 243 244
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);

  gst_element_class_add_pad_template (element_class,
David Schleef's avatar
David Schleef committed
245
      gst_static_pad_template_get (&src_factory));
246
  gst_element_class_add_pad_template (element_class,
David Schleef's avatar
David Schleef committed
247
      gst_static_pad_template_get (&audio_sink_factory));
248
  gst_element_class_add_pad_template (element_class,
David Schleef's avatar
David Schleef committed
249
      gst_static_pad_template_get (&video_sink_factory));
250

251 252 253 254
  gst_element_class_set_details_simple (element_class, "Avi muxer",
      "Codec/Muxer",
      "Muxes audio and video into an avi stream",
      "GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
255 256

  GST_DEBUG_CATEGORY_INIT (avimux_debug, "avimux", 0, "Muxer for AVI streams");
257 258
}

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
259
static void
260 261 262
gst_avi_mux_finalize (GObject * object)
{
  GstAviMux *mux = GST_AVI_MUX (object);
263 264 265 266 267 268 269 270 271 272 273 274 275 276
  GSList *node;

  /* completely free each sinkpad */
  node = mux->sinkpads;
  while (node) {
    GstAviPad *avipad = (GstAviPad *) node->data;

    node = node->next;

    gst_avi_mux_pad_reset (avipad, TRUE);
    g_free (avipad);
  }
  g_slist_free (mux->sinkpads);
  mux->sinkpads = NULL;
277 278 279 280 281 282 283 284 285 286 287

  g_free (mux->idx);
  mux->idx = NULL;

  gst_object_unref (mux->collect);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gst_avi_mux_class_init (GstAviMuxClass * klass)
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
288 289 290 291
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
292 293
  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
294

295
  parent_class = g_type_class_peek_parent (klass);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
296

297 298 299
  gobject_class->get_property = gst_avi_mux_get_property;
  gobject_class->set_property = gst_avi_mux_set_property;
  gobject_class->finalize = gst_avi_mux_finalize;
300

301 302 303 304
  g_object_class_install_property (gobject_class, ARG_BIGFILE,
      g_param_spec_boolean ("bigfile", "Bigfile Support (>2GB)",
          "Support for openDML-2.0 (big) AVI files", DEFAULT_BIGFILE,
          G_PARAM_READWRITE));
305

306 307 308 309
  gstelement_class->request_new_pad =
      GST_DEBUG_FUNCPTR (gst_avi_mux_request_new_pad);
  gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_avi_mux_release_pad);
  gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_avi_mux_change_state);
310 311
}

312 313
/* reset pad to initial state
 * free - if true, release all, not only stream related, data */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
314
static void
315
gst_avi_mux_pad_reset (GstAviPad * avipad, gboolean free)
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
316
{
317 318 319 320 321 322 323 324 325 326 327
  /* generic part */
  memset (&(avipad->hdr), 0, sizeof (gst_riff_strh));

  memset (&(avipad->idx[0]), 0, sizeof (avipad->idx));

  if (free) {
    g_free (avipad->tag);
    avipad->tag = NULL;
    g_free (avipad->idx_tag);
    avipad->idx_tag = NULL;
  }
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
328

329 330
  if (avipad->is_video) {
    GstAviVideoPad *vidpad = (GstAviVideoPad *) avipad;
331

332
    avipad->hdr.type = GST_MAKE_FOURCC ('v', 'i', 'd', 's');
333 334 335 336 337
    if (vidpad->vids_codec_data) {
      gst_buffer_unref (vidpad->vids_codec_data);
      vidpad->vids_codec_data = NULL;
    }

338 339 340 341 342
    if (vidpad->prepend_buffer) {
      gst_buffer_unref (vidpad->prepend_buffer);
      vidpad->prepend_buffer = NULL;
    }

343
    memset (&(vidpad->vids), 0, sizeof (gst_riff_strf_vids));
344
    memset (&(vidpad->vprp), 0, sizeof (gst_riff_vprp));
345 346 347
  } else {
    GstAviAudioPad *audpad = (GstAviAudioPad *) avipad;

348 349
    audpad->samples = 0;

350
    avipad->hdr.type = GST_MAKE_FOURCC ('a', 'u', 'd', 's');
351 352 353 354 355
    if (audpad->auds_codec_data) {
      gst_buffer_unref (audpad->auds_codec_data);
      audpad->auds_codec_data = NULL;
    }

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
    memset (&(audpad->auds), 0, sizeof (gst_riff_strf_auds));
  }
}

static void
gst_avi_mux_reset (GstAviMux * avimux)
{
  GSList *node, *newlist = NULL;

  /* free and reset each sinkpad */
  node = avimux->sinkpads;
  while (node) {
    GstAviPad *avipad = (GstAviPad *) node->data;

    node = node->next;

    gst_avi_mux_pad_reset (avipad, FALSE);
    /* if this pad has collectdata, keep it, otherwise dump it completely */
    if (avipad->collect)
      newlist = g_slist_append (newlist, avipad);
    else {
      gst_avi_mux_pad_reset (avipad, TRUE);
      g_free (avipad);
    }
  }

  /* free the old list of sinkpads, only keep the real collecting ones */
  g_slist_free (avimux->sinkpads);
  avimux->sinkpads = newlist;
385

386 387
  /* avi data */
  avimux->num_frames = 0;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
388
  memset (&(avimux->avi_hdr), 0, sizeof (gst_riff_avih));
389
  avimux->avi_hdr.max_bps = 10000000;
390
  avimux->codec_data_size = 0;
391

392 393 394 395 396
  if (avimux->tags_snap) {
    gst_tag_list_free (avimux->tags_snap);
    avimux->tags_snap = NULL;
  }

397
  g_free (avimux->idx);
398 399
  avimux->idx = NULL;

400
  /* state info */
401
  avimux->write_header = TRUE;
402 403

  /* tags */
404
  gst_tag_setter_reset_tags (GST_TAG_SETTER (avimux));
405 406 407 408 409 410 411 412
}

static void
gst_avi_mux_init (GstAviMux * avimux)
{
  avimux->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
  gst_pad_use_fixed_caps (avimux->srcpad);
  gst_element_add_pad (GST_ELEMENT (avimux), avimux->srcpad);
413

414
  /* property */
415 416 417 418 419 420
  avimux->enable_large_avi = DEFAULT_BIGFILE;

  avimux->collect = gst_collect_pads_new ();
  gst_collect_pads_set_function (avimux->collect,
      (GstCollectPadsFunction) (GST_DEBUG_FUNCPTR (gst_avi_mux_collect_pads)),
      avimux);
421 422 423

  /* set to clean state */
  gst_avi_mux_reset (avimux);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
424 425
}

426 427
static gboolean
gst_avi_mux_vidsink_set_caps (GstPad * pad, GstCaps * vscaps)
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
428 429
{
  GstAviMux *avimux;
430 431
  GstAviVideoPad *avipad;
  GstAviCollectData *collect_pad;
David Schleef's avatar
David Schleef committed
432
  GstStructure *structure;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
433
  const gchar *mimetype;
434
  const GValue *fps, *par;
435
  const GValue *codec_data;
436
  gint width, height;
437
  gint par_n, par_d;
438
  gboolean codec_data_in_headers = TRUE;
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
439

440
  avimux = GST_AVI_MUX (gst_pad_get_parent (pad));
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
441

442 443 444 445 446 447 448 449
  /* find stream data */
  collect_pad = (GstAviCollectData *) gst_pad_get_element_private (pad);
  g_assert (collect_pad);
  avipad = (GstAviVideoPad *) collect_pad->avipad;
  g_assert (avipad);
  g_assert (avipad->parent.is_video);
  g_assert (avipad->parent.hdr.type == GST_MAKE_FOURCC ('v', 'i', 'd', 's'));

450 451
  GST_DEBUG_OBJECT (avimux, "%s:%s, caps=%" GST_PTR_FORMAT,
      GST_DEBUG_PAD_NAME (pad), vscaps);
452

David Schleef's avatar
David Schleef committed
453 454
  structure = gst_caps_get_structure (vscaps, 0);
  mimetype = gst_structure_get_name (structure);
455

David Schleef's avatar
David Schleef committed
456
  /* global */
457 458
  avipad->vids.size = sizeof (gst_riff_strf_vids);
  avipad->vids.planes = 1;
459 460 461
  if (!gst_structure_get_int (structure, "width", &width) ||
      !gst_structure_get_int (structure, "height", &height)) {
    goto refuse_caps;
462
  }
David Schleef's avatar
David Schleef committed
463

464 465
  avipad->vids.width = width;
  avipad->vids.height = height;
466 467 468 469 470

  fps = gst_structure_get_value (structure, "framerate");
  if (fps == NULL || !GST_VALUE_HOLDS_FRACTION (fps))
    goto refuse_caps;

471 472
  avipad->parent.hdr.rate = gst_value_get_fraction_numerator (fps);
  avipad->parent.hdr.scale = gst_value_get_fraction_denominator (fps);
David Schleef's avatar
David Schleef committed
473

474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
  /* (pixel) aspect ratio data, if any */
  par = gst_structure_get_value (structure, "pixel-aspect-ratio");
  /* only use video properties header if there is non-trivial aspect info */
  if (par && GST_VALUE_HOLDS_FRACTION (par) &&
      ((par_n = gst_value_get_fraction_numerator (par)) !=
          (par_d = gst_value_get_fraction_denominator (par)))) {
    GValue to_ratio = { 0, };
    guint ratio_n, ratio_d;

    /* some fraction voodoo to obtain simplest possible ratio */
    g_value_init (&to_ratio, GST_TYPE_FRACTION);
    gst_value_set_fraction (&to_ratio, width * par_n, height * par_d);
    ratio_n = gst_value_get_fraction_numerator (&to_ratio);
    ratio_d = gst_value_get_fraction_denominator (&to_ratio);
    GST_DEBUG_OBJECT (avimux, "generating vprp data with aspect ratio %d/%d",
        ratio_n, ratio_d);
    /* simply fill in */
    avipad->vprp.vert_rate = avipad->parent.hdr.rate / avipad->parent.hdr.scale;
    avipad->vprp.hor_t_total = width;
    avipad->vprp.vert_lines = height;
    avipad->vprp.aspect = (ratio_n) << 16 | (ratio_d & 0xffff);
    avipad->vprp.width = width;
    avipad->vprp.height = height;
    avipad->vprp.fields = 1;
    avipad->vprp.field_info[0].compressed_bm_height = height;
    avipad->vprp.field_info[0].compressed_bm_width = width;
    avipad->vprp.field_info[0].valid_bm_height = height;
    avipad->vprp.field_info[0].valid_bm_width = width;
  }

David Schleef's avatar
David Schleef committed
504 505 506 507
  if (!strcmp (mimetype, "video/x-raw-yuv")) {
    guint32 format;

    gst_structure_get_fourcc (structure, "format", &format);
508
    avipad->vids.compression = format;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
509 510
    switch (format) {
      case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'):
511
        avipad->vids.bit_cnt = 16;
512
        break;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
513
      case GST_MAKE_FOURCC ('I', '4', '2', '0'):
514
        avipad->vids.bit_cnt = 12;
515
        break;
David Schleef's avatar
David Schleef committed
516 517
    }
  } else {
518 519
    avipad->vids.bit_cnt = 24;
    avipad->vids.compression = 0;
David Schleef's avatar
David Schleef committed
520 521 522

    /* find format */
    if (!strcmp (mimetype, "video/x-huffyuv")) {
523
      avipad->vids.compression = GST_MAKE_FOURCC ('H', 'F', 'Y', 'U');
524
    } else if (!strcmp (mimetype, "image/jpeg")) {
525
      avipad->vids.compression = GST_MAKE_FOURCC ('M', 'J', 'P', 'G');
David Schleef's avatar
David Schleef committed
526 527
    } else if (!strcmp (mimetype, "video/x-divx")) {
      gint divxversion;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
528

David Schleef's avatar
David Schleef committed
529 530
      gst_structure_get_int (structure, "divxversion", &divxversion);
      switch (divxversion) {
531
        case 3:
532
          avipad->vids.compression = GST_MAKE_FOURCC ('D', 'I', 'V', '3');
533 534
          break;
        case 4:
535
          avipad->vids.compression = GST_MAKE_FOURCC ('D', 'I', 'V', 'X');
536 537
          break;
        case 5:
538
          avipad->vids.compression = GST_MAKE_FOURCC ('D', 'X', '5', '0');
539
          break;
540
      }
David Schleef's avatar
David Schleef committed
541
    } else if (!strcmp (mimetype, "video/x-xvid")) {
542
      avipad->vids.compression = GST_MAKE_FOURCC ('X', 'V', 'I', 'D');
David Schleef's avatar
David Schleef committed
543
    } else if (!strcmp (mimetype, "video/x-3ivx")) {
544
      avipad->vids.compression = GST_MAKE_FOURCC ('3', 'I', 'V', '2');
545
    } else if (gst_structure_has_name (structure, "video/x-msmpeg")) {
David Schleef's avatar
David Schleef committed
546
      gint msmpegversion;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
547

David Schleef's avatar
David Schleef committed
548 549
      gst_structure_get_int (structure, "msmpegversion", &msmpegversion);
      switch (msmpegversion) {
550
        case 41:
551
          avipad->vids.compression = GST_MAKE_FOURCC ('M', 'P', 'G', '4');
552 553
          break;
        case 42:
554
          avipad->vids.compression = GST_MAKE_FOURCC ('M', 'P', '4', '2');
555 556
          break;
        case 43:
557
          avipad->vids.compression = GST_MAKE_FOURCC ('M', 'P', '4', '3');
558
          break;
559 560 561
        default:
          GST_INFO ("unhandled msmpegversion : %d, fall back to fourcc=MPEG",
              msmpegversion);
562
          avipad->vids.compression = GST_MAKE_FOURCC ('M', 'P', 'E', 'G');
563
          break;
564
      }
David Schleef's avatar
David Schleef committed
565
    } else if (!strcmp (mimetype, "video/x-dv")) {
566
      avipad->vids.compression = GST_MAKE_FOURCC ('D', 'V', 'S', 'D');
David Schleef's avatar
David Schleef committed
567
    } else if (!strcmp (mimetype, "video/x-h263")) {
568
      avipad->vids.compression = GST_MAKE_FOURCC ('H', '2', '6', '3');
569 570
    } else if (!strcmp (mimetype, "video/x-h264")) {
      avipad->vids.compression = GST_MAKE_FOURCC ('H', '2', '6', '4');
David Schleef's avatar
David Schleef committed
571
    } else if (!strcmp (mimetype, "video/mpeg")) {
572 573 574 575 576 577
      gint mpegversion;

      gst_structure_get_int (structure, "mpegversion", &mpegversion);

      switch (mpegversion) {
        case 2:
578
          avipad->vids.compression = GST_MAKE_FOURCC ('M', 'P', 'G', '2');
579 580 581
          break;
        case 4:
          /* mplayer/ffmpeg might not work with DIVX, but with FMP4 */
582
          avipad->vids.compression = GST_MAKE_FOURCC ('D', 'I', 'V', 'X');
583 584 585 586 587

          /* DIVX/XVID in AVI store the codec_data chunk as part of the
             first data buffer. So for this case, we prepend the codec_data
             blob (if any) to that first buffer */
          codec_data_in_headers = FALSE;
588 589 590 591
          break;
        default:
          GST_INFO ("unhandled mpegversion : %d, fall back to fourcc=MPEG",
              mpegversion);
592
          avipad->vids.compression = GST_MAKE_FOURCC ('M', 'P', 'E', 'G');
593 594
          break;
      }
595 596
    } else if (!strcmp (mimetype, "video/x-dirac")) {
      avipad->vids.compression = GST_MAKE_FOURCC ('d', 'r', 'a', 'c');
Thiago Santos's avatar
Thiago Santos committed
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
    } else if (!strcmp (mimetype, "video/x-wmv")) {
      gint wmvversion;

      if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) {
        switch (wmvversion) {
          case 1:
            avipad->vids.compression = GST_MAKE_FOURCC ('W', 'M', 'V', '1');
            break;
          case 2:
            avipad->vids.compression = GST_MAKE_FOURCC ('W', 'M', 'V', '2');
            break;
          case 3:
            avipad->vids.compression = GST_MAKE_FOURCC ('W', 'M', 'V', '3');
          default:
            break;
        }
      }
614 615
    } else if (!strcmp (mimetype, "image/x-jpc")) {
      avipad->vids.compression = GST_MAKE_FOURCC ('M', 'J', '2', 'C');
David Schleef's avatar
David Schleef committed
616
    }
617

618
    if (!avipad->vids.compression)
619
      goto refuse_caps;
620 621
  }

622 623 624 625 626 627 628 629 630 631 632 633 634 635
  /* codec initialization data, if any */
  codec_data = gst_structure_get_value (structure, "codec_data");
  if (codec_data) {
    if (codec_data_in_headers) {
      avipad->vids_codec_data = gst_value_get_buffer (codec_data);
      gst_buffer_ref (avipad->vids_codec_data);
      /* keep global track of size */
      avimux->codec_data_size += GST_BUFFER_SIZE (avipad->vids_codec_data);
    } else {
      avipad->prepend_buffer =
          gst_buffer_ref (gst_value_get_buffer (codec_data));
    }
  }

636 637 638 639 640 641 642
  avipad->parent.hdr.fcc_handler = avipad->vids.compression;
  avipad->vids.image_size = avipad->vids.height * avipad->vids.width;
  /* hm, maybe why avi only handles one stream well ... */
  avimux->avi_hdr.width = avipad->vids.width;
  avimux->avi_hdr.height = avipad->vids.height;
  avimux->avi_hdr.us_frame = 1000000. * avipad->parent.hdr.scale /
      avipad->parent.hdr.rate;
643 644

  gst_object_unref (avimux);
645
  return TRUE;
646

647 648 649 650 651 652
refuse_caps:
  {
    GST_WARNING_OBJECT (avimux, "refused caps %" GST_PTR_FORMAT, vscaps);
    gst_object_unref (avimux);
    return FALSE;
  }
653
}
Wim Taymans's avatar
Wim Taymans committed
654

655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744
static GstFlowReturn
gst_avi_mux_audsink_scan_mpeg_audio (GstAviMux * avimux, GstAviPad * avipad,
    GstBuffer * buffer)
{
  guint8 *data;
  guint size;
  guint spf;
  guint32 header;
  gulong layer;
  gulong version;
  gint lsf, mpg25;

  data = GST_BUFFER_DATA (buffer);
  size = GST_BUFFER_SIZE (buffer);

  if (size < 4)
    goto not_parsed;

  header = GST_READ_UINT32_BE (data);

  if ((header & 0xffe00000) != 0xffe00000)
    goto not_parsed;

  /* thanks go to mp3parse */
  if (header & (1 << 20)) {
    lsf = (header & (1 << 19)) ? 0 : 1;
    mpg25 = 0;
  } else {
    lsf = 1;
    mpg25 = 1;
  }

  version = 1 + lsf + mpg25;
  layer = 4 - ((header >> 17) & 0x3);

  /* see http://www.codeproject.com/audio/MPEGAudioInfo.asp */
  if (layer == 1)
    spf = 384;
  else if (layer == 2)
    spf = 1152;
  else if (version == 1) {
    spf = 1152;
  } else {
    /* MPEG-2 or "2.5" */
    spf = 576;
  }

  if (G_UNLIKELY (avipad->hdr.scale <= 1))
    avipad->hdr.scale = spf;
  else if (G_UNLIKELY (avipad->hdr.scale != spf)) {
    GST_WARNING_OBJECT (avimux, "input mpeg audio has varying frame size");
    goto cbr_fallback;
  }

  return GST_FLOW_OK;

  /* EXITS */
not_parsed:
  {
    GST_WARNING_OBJECT (avimux, "input mpeg audio is not parsed");
    /* fall-through */
  }
cbr_fallback:
  {
    GST_WARNING_OBJECT (avimux, "falling back to CBR muxing");
    avipad->hdr.scale = 1;
    /* no need to check further */
    avipad->hook = NULL;
    return GST_FLOW_OK;
  }
}

static void
gst_avi_mux_audsink_set_fields (GstAviMux * avimux, GstAviAudioPad * avipad)
{
  if (avipad->parent.hdr.scale > 1) {
    /* vbr case: fixed duration per frame/chunk */
    avipad->parent.hdr.rate = avipad->auds.rate;
    avipad->parent.hdr.samplesize = 0;
    /* FIXME ?? some rumours say this should be largest audio chunk size */
    avipad->auds.blockalign = avipad->parent.hdr.scale;
  } else {
    /* by spec, hdr.rate is av_bps related, is calculated that way in stop_file,
     * and reduces to sample rate in PCM like cases */
    avipad->parent.hdr.rate = avipad->auds.av_bps / avipad->auds.blockalign;
    avipad->parent.hdr.samplesize = avipad->auds.blockalign;
    avipad->parent.hdr.scale = 1;
  }
}

745 746
static gboolean
gst_avi_mux_audsink_set_caps (GstPad * pad, GstCaps * vscaps)
747 748
{
  GstAviMux *avimux;
749 750
  GstAviAudioPad *avipad;
  GstAviCollectData *collect_pad;
David Schleef's avatar
David Schleef committed
751
  GstStructure *structure;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
752
  const gchar *mimetype;
753
  const GValue *codec_data;
754
  gint channels, rate;
755

756
  avimux = GST_AVI_MUX (gst_pad_get_parent (pad));
757

758 759 760 761 762 763 764 765
  /* find stream data */
  collect_pad = (GstAviCollectData *) gst_pad_get_element_private (pad);
  g_assert (collect_pad);
  avipad = (GstAviAudioPad *) collect_pad->avipad;
  g_assert (avipad);
  g_assert (!avipad->parent.is_video);
  g_assert (avipad->parent.hdr.type == GST_MAKE_FOURCC ('a', 'u', 'd', 's'));

766 767
  GST_DEBUG_OBJECT (avimux, "%s:%s, caps=%" GST_PTR_FORMAT,
      GST_DEBUG_PAD_NAME (pad), vscaps);
768

David Schleef's avatar
David Schleef committed
769 770
  structure = gst_caps_get_structure (vscaps, 0);
  mimetype = gst_structure_get_name (structure);
771

David Schleef's avatar
David Schleef committed
772
  /* we want these for all */
773 774 775 776 777
  if (!gst_structure_get_int (structure, "channels", &channels) ||
      !gst_structure_get_int (structure, "rate", &rate)) {
    goto refuse_caps;
  }

778 779
  avipad->auds.channels = channels;
  avipad->auds.rate = rate;
780

781 782 783 784 785 786 787 788 789
  /* codec initialization data, if any */
  codec_data = gst_structure_get_value (structure, "codec_data");
  if (codec_data) {
    avipad->auds_codec_data = gst_value_get_buffer (codec_data);
    gst_buffer_ref (avipad->auds_codec_data);
    /* keep global track of size */
    avimux->codec_data_size += GST_BUFFER_SIZE (avipad->auds_codec_data);
  }

David Schleef's avatar
David Schleef committed
790
  if (!strcmp (mimetype, "audio/x-raw-int")) {
791
    gint width, depth;
792
    gboolean signedness;
793

794
    avipad->auds.format = GST_RIFF_WAVE_FORMAT_PCM;
795

796
    if (!gst_structure_get_int (structure, "width", &width) ||
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
        !gst_structure_get_int (structure, "depth", &depth) ||
        !gst_structure_get_boolean (structure, "signed", &signedness)) {
      GST_DEBUG_OBJECT (avimux,
          "broken caps, width/depth/signed field missing");
      goto refuse_caps;
    }

    /* no clear place to put different values for these while keeping to spec */
    if (width != depth) {
      GST_DEBUG_OBJECT (avimux, "width must be same as depth!");
      goto refuse_caps;
    }

    /* because that's the way the caps will be recreated from riff data */
    if ((width == 8 && signedness) || (width == 16 && !signedness)) {
      GST_DEBUG_OBJECT (avimux,
          "8-bit PCM must be unsigned, 16-bit PCM signed");
814 815 816
      goto refuse_caps;
    }

817 818
    avipad->auds.blockalign = width;
    avipad->auds.size = (width == 8) ? 8 : depth;
819

David Schleef's avatar
David Schleef committed
820
    /* set some more info straight */
821 822 823
    avipad->auds.blockalign /= 8;
    avipad->auds.blockalign *= avipad->auds.channels;
    avipad->auds.av_bps = avipad->auds.blockalign * avipad->auds.rate;
824
  } else {
825
    avipad->auds.format = 0;
826 827 828 829
    /* set some defaults */
    avipad->auds.blockalign = 1;
    avipad->auds.av_bps = 0;
    avipad->auds.size = 16;
David Schleef's avatar
David Schleef committed
830 831

    if (!strcmp (mimetype, "audio/mpeg")) {
832
      gint mpegversion;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
833

834 835 836 837
      gst_structure_get_int (structure, "mpegversion", &mpegversion);
      switch (mpegversion) {
        case 1:{
          gint layer = 3;
838
          gboolean parsed = FALSE;
839 840

          gst_structure_get_int (structure, "layer", &layer);
841
          gst_structure_get_boolean (structure, "parsed", &parsed);
842 843 844 845 846 847 848 849 850
          switch (layer) {
            case 3:
              avipad->auds.format = GST_RIFF_WAVE_FORMAT_MPEGL3;
              break;
            case 1:
            case 2:
              avipad->auds.format = GST_RIFF_WAVE_FORMAT_MPEGL12;
              break;
          }
851 852 853 854 855 856 857 858
          if (parsed) {
            /* treat as VBR, should also cover CBR case;
             * setup hook to parse frame header and determine spf */
            avipad->parent.hook = gst_avi_mux_audsink_scan_mpeg_audio;
          } else {
            GST_WARNING_OBJECT (avimux, "unparsed MPEG audio input (?), "
                "doing CBR muxing");
          }
859
          break;
860 861
        }
        case 4:
862 863
        {
          GstBuffer *codec_data_buf = avipad->auds_codec_data;
Thiago Santos's avatar
Thiago Santos committed
864
          const gchar *stream_format;
865 866
          guint codec;

Thiago Santos's avatar
Thiago Santos committed
867 868
          stream_format = gst_structure_get_string (structure, "stream-format");
          if (stream_format) {
869
            if (strcmp (stream_format, "raw") != 0) {
Thiago Santos's avatar
Thiago Santos committed
870
              GST_WARNING_OBJECT (avimux, "AAC's stream format '%s' is not "
871
                  "supported, please use 'raw'", stream_format);
Thiago Santos's avatar
Thiago Santos committed
872 873 874 875
              break;
            }
          } else {
            GST_WARNING_OBJECT (avimux, "AAC's stream-format not specified, "
876
                "assuming 'raw'");
Thiago Santos's avatar
Thiago Santos committed
877 878
          }

879 880 881 882 883
          /* vbr case needs some special handling */
          if (!codec_data_buf || GST_BUFFER_SIZE (codec_data_buf) < 2) {
            GST_WARNING_OBJECT (avimux, "no (valid) codec_data for AAC audio");
            break;
          }
884
          avipad->auds.format = GST_RIFF_WAVE_FORMAT_AAC;
885 886 887
          /* need to determine frame length */
          codec = GST_READ_UINT16_BE (GST_BUFFER_DATA (codec_data_buf));
          avipad->parent.hdr.scale = (codec & 0x4) ? 960 : 1024;
888
          break;
889
        }
890
      }
David Schleef's avatar
David Schleef committed
891
    } else if (!strcmp (mimetype, "audio/x-vorbis")) {
892
      avipad->auds.format = GST_RIFF_WAVE_FORMAT_VORBIS3;
David Schleef's avatar
David Schleef committed
893
    } else if (!strcmp (mimetype, "audio/x-ac3")) {
894
      avipad->auds.format = GST_RIFF_WAVE_FORMAT_A52;
895 896 897 898 899 900 901 902 903 904
    } else if (!strcmp (mimetype, "audio/x-alaw")) {
      avipad->auds.format = GST_RIFF_WAVE_FORMAT_ALAW;
      avipad->auds.size = 8;
      avipad->auds.blockalign = avipad->auds.channels;
      avipad->auds.av_bps = avipad->auds.blockalign * avipad->auds.rate;
    } else if (!strcmp (mimetype, "audio/x-mulaw")) {
      avipad->auds.format = GST_RIFF_WAVE_FORMAT_MULAW;
      avipad->auds.size = 8;
      avipad->auds.blockalign = avipad->auds.channels;
      avipad->auds.av_bps = avipad->auds.blockalign * avipad->auds.rate;
Thiago Santos's avatar
Thiago Santos committed
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
    } else if (!strcmp (mimetype, "audio/x-wma")) {
      gint version;
      gint bitrate;
      gint block_align;

      if (gst_structure_get_int (structure, "wmaversion", &version)) {
        switch (version) {
          case 1:
            avipad->auds.format = GST_RIFF_WAVE_FORMAT_WMAV1;
            break;
          case 2:
            avipad->auds.format = GST_RIFF_WAVE_FORMAT_WMAV2;
            break;
          default:
            break;
        }
      }

      if (avipad->auds.format != 0) {
        if (gst_structure_get_int (structure, "block_align", &block_align)) {
          avipad->auds.blockalign = block_align;
        }
        if (gst_structure_get_int (structure, "bitrate", &bitrate)) {
          avipad->auds.av_bps = bitrate / 8;
        }
      }
David Schleef's avatar
David Schleef committed
931
    }
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
932 933
  }

934 935 936
  if (!avipad->auds.format)
    goto refuse_caps;

937
  gst_avi_mux_audsink_set_fields (avimux, avipad);
938

939 940
  gst_object_unref (avimux);
  return TRUE;
941

942 943 944 945 946 947
refuse_caps:
  {
    GST_WARNING_OBJECT (avimux, "refused caps %" GST_PTR_FORMAT, vscaps);
    gst_object_unref (avimux);
    return FALSE;
  }
Andy Wingo Wingo's avatar