gstavidemux.c 161 KB
Newer Older
1 2
/* GStreamer
 * Copyright (C) <1999> Erik Walthinsen <omega@temple-baptist.com>
3
 * Copyright (C) <2006> Nokia Corporation (contact <stefan.kost@nokia.com>)
4
 * Copyright (C) <2009-2010> STEricsson <benjamin.gaignard@stericsson.com>
Andy Wingo's avatar
Andy Wingo committed
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
/* Element-Checklist-Version: 5 */
Andy Wingo's avatar
Andy Wingo committed
22

23 24 25 26
/**
 * SECTION:element-avidemux
 *
 * Demuxes an .avi file into raw or compressed audio and/or video streams.
27
 *
28
 * This element supports both push and pull-based scheduling, depending on the
29
 * capabilities of the upstream elements.
30 31
 *
 * <refsect2>
32
 * <title>Example launch line</title>
33
 * |[
34
 * gst-launch filesrc location=test.avi ! avidemux name=demux  demux.audio_00 ! decodebin ! audioconvert ! audioresample ! autoaudiosink   demux.video_00 ! queue ! decodebin ! ffmpegcolorspace ! videoscale ! autovideosink
35
 * ]| Play (parse and decode) an .avi file and try to output it to
36 37 38 39 40
 * an automatically detected soundcard and videosink. If the AVI file contains
 * compressed audio or video data, this will only work if you have the
 * right decoder elements/plugins installed.
 * </refsect2>
 *
41
 * Last reviewed on 2006-12-29 (0.10.6)
42 43
 */

44 45 46
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
47

Andy Wingo's avatar
Andy Wingo committed
48
#include <string.h>
49
#include <stdio.h>
Andy Wingo's avatar
Andy Wingo committed
50

51
#include "gst/riff/riff-media.h"
Andy Wingo's avatar
Andy Wingo committed
52
#include "gstavidemux.h"
53
#include "avi-ids.h"
54
#include <gst/gst-i18n-plugin.h>
55
#include <gst/base/gstadapter.h>
Andy Wingo's avatar
Andy Wingo committed
56

Wim Taymans's avatar
Wim Taymans committed
57

58
#define DIV_ROUND_UP(s,v) (((s) + ((v)-1)) / (v))
Wim Taymans's avatar
Wim Taymans committed
59

Stefan Kost's avatar
Stefan Kost committed
60 61 62 63 64 65
#define GST_AVI_KEYFRAME 1
#define ENTRY_IS_KEYFRAME(e) ((e)->flags == GST_AVI_KEYFRAME)
#define ENTRY_SET_KEYFRAME(e) ((e)->flags = GST_AVI_KEYFRAME)
#define ENTRY_UNSET_KEYFRAME(e) ((e)->flags = 0)


66 67
GST_DEBUG_CATEGORY_STATIC (avidemux_debug);
#define GST_CAT_DEFAULT avidemux_debug
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
68

69
static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
70 71 72 73
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-msvideo")
    );
74

75 76 77 78 79 80 81
#ifndef GST_DISABLE_GST_DEBUG
static const char *const snap_types[2][2] = {
  {"any", "before"},
  {"after", "nearest"},
};
#endif

82
static void gst_avi_demux_finalize (GObject * object);
83

84
static void gst_avi_demux_reset (GstAviDemux * avi);
85

86
#if 0
87
static const GstEventMask *gst_avi_demux_get_event_mask (GstPad * pad);
88
#endif
Wim Taymans's avatar
Wim Taymans committed
89 90
static gboolean gst_avi_demux_handle_src_event (GstPad * pad,
    GstObject * parent, GstEvent * event);
91
static gboolean gst_avi_demux_handle_sink_event (GstPad * pad,
Wim Taymans's avatar
Wim Taymans committed
92
    GstObject * parent, GstEvent * event);
93
static gboolean gst_avi_demux_push_event (GstAviDemux * avi, GstEvent * event);
94 95

#if 0
96
static const GstFormat *gst_avi_demux_get_src_formats (GstPad * pad);
97
#endif
Wim Taymans's avatar
Wim Taymans committed
98 99
static gboolean gst_avi_demux_handle_src_query (GstPad * pad,
    GstObject * parent, GstQuery * query);
100 101 102
static gboolean gst_avi_demux_src_convert (GstPad * pad, GstFormat src_format,
    gint64 src_value, GstFormat * dest_format, gint64 * dest_value);

103 104 105
static gboolean gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment);
static gboolean gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad,
    GstEvent * event);
106 107
static gboolean gst_avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad,
    GstEvent * event);
108
static void gst_avi_demux_loop (GstPad * pad);
109 110
static gboolean gst_avi_demux_sink_activate (GstPad * sinkpad,
    GstObject * parent);
Wim Taymans's avatar
Wim Taymans committed
111 112
static gboolean gst_avi_demux_sink_activate_mode (GstPad * sinkpad,
    GstObject * parent, GstPadMode mode, gboolean active);
Wim Taymans's avatar
Wim Taymans committed
113 114
static GstFlowReturn gst_avi_demux_chain (GstPad * pad, GstObject * parent,
    GstBuffer * buf);
115
#if 0
116 117
static void gst_avi_demux_set_index (GstElement * element, GstIndex * index);
static GstIndex *gst_avi_demux_get_index (GstElement * element);
118
#endif
119 120
static GstStateChangeReturn gst_avi_demux_change_state (GstElement * element,
    GstStateChange transition);
121 122 123 124
static void gst_avi_demux_calculate_durations_from_index (GstAviDemux * avi);
static void gst_avi_demux_get_buffer_info (GstAviDemux * avi,
    GstAviStream * stream, guint entry_n, GstClockTime * timestamp,
    GstClockTime * ts_end, guint64 * offset, guint64 * offset_end);
Andy Wingo's avatar
Andy Wingo committed
125

126 127
static void gst_avi_demux_parse_idit (GstAviDemux * avi, GstBuffer * buf);

128
/* GObject methods */
129

Wim Taymans's avatar
Wim Taymans committed
130 131
#define gst_avi_demux_parent_class parent_class
G_DEFINE_TYPE (GstAviDemux, gst_avi_demux, GST_TYPE_ELEMENT);
Andy Wingo's avatar
Andy Wingo committed
132

133
static void
Wim Taymans's avatar
Wim Taymans committed
134
gst_avi_demux_class_init (GstAviDemuxClass * klass)
135
{
Wim Taymans's avatar
Wim Taymans committed
136 137
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
  GObjectClass *gobject_class = (GObjectClass *) klass;
138 139
  GstPadTemplate *videosrctempl, *audiosrctempl, *subsrctempl;
  GstCaps *audcaps, *vidcaps, *subcaps;
140

Wim Taymans's avatar
Wim Taymans committed
141 142 143 144 145 146 147
  GST_DEBUG_CATEGORY_INIT (avidemux_debug, "avidemux",
      0, "Demuxer for AVI streams");

  gobject_class->finalize = gst_avi_demux_finalize;

  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_avi_demux_change_state);
148
#if 0
Wim Taymans's avatar
Wim Taymans committed
149 150
  gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_avi_demux_set_index);
  gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_avi_demux_get_index);
151
#endif
Wim Taymans's avatar
Wim Taymans committed
152

153
  audcaps = gst_riff_create_audio_template_caps ();
154
  gst_caps_append (audcaps, gst_caps_new_empty_simple ("audio/x-avi-unknown"));
Wim Taymans's avatar
Wim Taymans committed
155
  audiosrctempl = gst_pad_template_new ("audio_%u",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
156
      GST_PAD_SRC, GST_PAD_SOMETIMES, audcaps);
157

David Schleef's avatar
David Schleef committed
158 159
  vidcaps = gst_riff_create_video_template_caps ();
  gst_caps_append (vidcaps, gst_riff_create_iavs_template_caps ());
160
  gst_caps_append (vidcaps, gst_caps_new_empty_simple ("video/x-avi-unknown"));
Wim Taymans's avatar
Wim Taymans committed
161
  videosrctempl = gst_pad_template_new ("video_%u",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
162
      GST_PAD_SRC, GST_PAD_SOMETIMES, vidcaps);
163

164
  subcaps = gst_caps_new_empty_simple ("application/x-subtitle-avi");
Wim Taymans's avatar
Wim Taymans committed
165
  subsrctempl = gst_pad_template_new ("subtitle_%u",
166
      GST_PAD_SRC, GST_PAD_SOMETIMES, subcaps);
Wim Taymans's avatar
Wim Taymans committed
167 168 169 170
  gst_element_class_add_pad_template (gstelement_class, audiosrctempl);
  gst_element_class_add_pad_template (gstelement_class, videosrctempl);
  gst_element_class_add_pad_template (gstelement_class, subsrctempl);
  gst_element_class_add_pad_template (gstelement_class,
David Schleef's avatar
David Schleef committed
171
      gst_static_pad_template_get (&sink_templ));
Wim Taymans's avatar
Wim Taymans committed
172

173
  gst_element_class_set_static_metadata (gstelement_class, "Avi demuxer",
174 175 176 177 178
      "Codec/Demuxer",
      "Demultiplex an avi file into audio and video",
      "Erik Walthinsen <omega@cse.ogi.edu>, "
      "Wim Taymans <wim.taymans@chello.be>, "
      "Thijs Vermeir <thijsvermeir@gmail.com>");
179 180
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
181 182
static void
gst_avi_demux_init (GstAviDemux * avi)
183
{
184
  avi->sinkpad = gst_pad_new_from_static_template (&sink_templ, "sink");
185 186
  gst_pad_set_activate_function (avi->sinkpad,
      GST_DEBUG_FUNCPTR (gst_avi_demux_sink_activate));
Wim Taymans's avatar
Wim Taymans committed
187 188
  gst_pad_set_activatemode_function (avi->sinkpad,
      GST_DEBUG_FUNCPTR (gst_avi_demux_sink_activate_mode));
189 190 191 192
  gst_pad_set_chain_function (avi->sinkpad,
      GST_DEBUG_FUNCPTR (gst_avi_demux_chain));
  gst_pad_set_event_function (avi->sinkpad,
      GST_DEBUG_FUNCPTR (gst_avi_demux_handle_sink_event));
193
  gst_element_add_pad (GST_ELEMENT_CAST (avi), avi->sinkpad);
Wim Taymans's avatar
Wim Taymans committed
194

195
  avi->adapter = gst_adapter_new ();
Wim Taymans's avatar
Wim Taymans committed
196

197
  gst_avi_demux_reset (avi);
Wim Taymans's avatar
Wim Taymans committed
198 199

  GST_OBJECT_FLAG_SET (avi, GST_ELEMENT_FLAG_INDEXABLE);
Wim Taymans's avatar
Wim Taymans committed
200 201
}

202
static void
203
gst_avi_demux_finalize (GObject * object)
204 205 206
{
  GstAviDemux *avi = GST_AVI_DEMUX (object);

207 208 209
  GST_DEBUG ("AVI: finalize");

  g_object_unref (avi->adapter);
210

211
  G_OBJECT_CLASS (parent_class)->finalize (object);
212 213
}

Wim Taymans's avatar
Wim Taymans committed
214 215 216 217 218 219 220 221 222 223 224 225 226
static void
gst_avi_demux_reset_stream (GstAviDemux * avi, GstAviStream * stream)
{
  g_free (stream->strh);
  g_free (stream->strf.data);
  g_free (stream->name);
  g_free (stream->index);
  g_free (stream->indexes);
  if (stream->initdata)
    gst_buffer_unref (stream->initdata);
  if (stream->extradata)
    gst_buffer_unref (stream->extradata);
  if (stream->pad) {
227 228
    if (stream->exposed) {
      gst_pad_set_active (stream->pad, FALSE);
229
      gst_element_remove_pad (GST_ELEMENT_CAST (avi), stream->pad);
230 231
    } else
      gst_object_unref (stream->pad);
Wim Taymans's avatar
Wim Taymans committed
232 233 234 235 236 237 238 239
  }
  if (stream->taglist) {
    gst_tag_list_free (stream->taglist);
    stream->taglist = NULL;
  }
  memset (stream, 0, sizeof (GstAviStream));
}

240
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
241
gst_avi_demux_reset (GstAviDemux * avi)
242
{
243
  gint i;
244

245 246
  GST_DEBUG ("AVI: reset");

Wim Taymans's avatar
Wim Taymans committed
247 248
  for (i = 0; i < avi->num_streams; i++)
    gst_avi_demux_reset_stream (avi, &avi->stream[i]);
249

250
  avi->header_state = GST_AVI_DEMUX_HEADER_TAG_LIST;
251 252 253
  avi->num_streams = 0;
  avi->num_v_streams = 0;
  avi->num_a_streams = 0;
254
  avi->num_t_streams = 0;
255
  avi->main_stream = -1;
256

257
  avi->state = GST_AVI_DEMUX_START;
258
  avi->offset = 0;
Wim Taymans's avatar
Wim Taymans committed
259
  avi->building_index = FALSE;
260

261
  avi->index_offset = 0;
262 263
  g_free (avi->avih);
  avi->avih = NULL;
264

265
#if 0
266 267 268
  if (avi->element_index)
    gst_object_unref (avi->element_index);
  avi->element_index = NULL;
269
#endif
270

Wim Taymans's avatar
Wim Taymans committed
271 272 273
  if (avi->seg_event) {
    gst_event_unref (avi->seg_event);
    avi->seg_event = NULL;
274
  }
Wim Taymans's avatar
Wim Taymans committed
275 276 277 278
  if (avi->seek_event) {
    gst_event_unref (avi->seek_event);
    avi->seek_event = NULL;
  }
279

280 281 282 283
  if (avi->globaltags)
    gst_tag_list_free (avi->globaltags);
  avi->globaltags = NULL;

284
  avi->got_tags = TRUE;         /* we always want to push global tags */
285
  avi->have_eos = FALSE;
286
  avi->seekable = TRUE;
287

288 289
  gst_adapter_clear (avi->adapter);

290
  gst_segment_init (&avi->segment, GST_FORMAT_TIME);
291 292
}

293

294 295
/* GstElement methods */

296
#if 0
297
static const GstFormat *
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
298
gst_avi_demux_get_src_formats (GstPad * pad)
Andy Wingo's avatar
Andy Wingo committed
299
{
Wim Taymans's avatar
Wim Taymans committed
300
  GstAviStream *stream = gst_pad_get_element_private (pad);
Andy Wingo's avatar
Andy Wingo committed
301

302 303 304 305 306 307 308 309 310 311 312
  static const GstFormat src_a_formats[] = {
    GST_FORMAT_TIME,
    GST_FORMAT_BYTES,
    GST_FORMAT_DEFAULT,
    0
  };
  static const GstFormat src_v_formats[] = {
    GST_FORMAT_TIME,
    GST_FORMAT_DEFAULT,
    0
  };
Andy Wingo's avatar
Andy Wingo committed
313

314
  return (stream->strh->type == GST_RIFF_FCC_auds ?
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
315
      src_a_formats : src_v_formats);
316
}
317
#endif
318

319 320
/* assumes stream->strf.auds->av_bps != 0 */
static inline GstClockTime
Wim Taymans's avatar
Wim Taymans committed
321
avi_stream_convert_bytes_to_time_unchecked (GstAviStream * stream,
322 323
    guint64 bytes)
{
Wim Taymans's avatar
Wim Taymans committed
324 325
  return gst_util_uint64_scale_int (bytes, GST_SECOND,
      stream->strf.auds->av_bps);
326 327
}

Wim Taymans's avatar
Wim Taymans committed
328 329 330 331
static inline guint64
avi_stream_convert_time_to_bytes_unchecked (GstAviStream * stream,
    GstClockTime time)
{
Wim Taymans's avatar
Wim Taymans committed
332 333
  return gst_util_uint64_scale_int (time, stream->strf.auds->av_bps,
      GST_SECOND);
Wim Taymans's avatar
Wim Taymans committed
334 335
}

336 337
/* assumes stream->strh->rate != 0 */
static inline GstClockTime
Wim Taymans's avatar
Wim Taymans committed
338
avi_stream_convert_frames_to_time_unchecked (GstAviStream * stream,
339 340 341 342 343 344
    guint64 frames)
{
  return gst_util_uint64_scale (frames, stream->strh->scale * GST_SECOND,
      stream->strh->rate);
}

Wim Taymans's avatar
Wim Taymans committed
345 346 347 348 349 350 351 352
static inline guint64
avi_stream_convert_time_to_frames_unchecked (GstAviStream * stream,
    GstClockTime time)
{
  return gst_util_uint64_scale (time, stream->strh->rate,
      stream->strh->scale * GST_SECOND);
}

353
static gboolean
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
354 355 356
gst_avi_demux_src_convert (GstPad * pad,
    GstFormat src_format,
    gint64 src_value, GstFormat * dest_format, gint64 * dest_value)
357
{
Wim Taymans's avatar
Wim Taymans committed
358
  GstAviStream *stream = gst_pad_get_element_private (pad);
359
  gboolean res = TRUE;
360

361
  GST_LOG_OBJECT (pad,
362 363 364
      "Received  src_format:%s, src_value:%" G_GUINT64_FORMAT
      ", dest_format:%s", gst_format_get_name (src_format), src_value,
      gst_format_get_name (*dest_format));
365

366
  if (G_UNLIKELY (src_format == *dest_format)) {
367
    *dest_value = src_value;
368 369
    goto done;
  }
370
  if (G_UNLIKELY (!stream->strh || !stream->strf.data)) {
371 372
    res = FALSE;
    goto done;
373
  }
374 375 376
  if (G_UNLIKELY (stream->strh->type == GST_RIFF_FCC_vids &&
          (src_format == GST_FORMAT_BYTES
              || *dest_format == GST_FORMAT_BYTES))) {
377 378 379
    res = FALSE;
    goto done;
  }
380

381 382 383
  switch (src_format) {
    case GST_FORMAT_TIME:
      switch (*dest_format) {
384
        case GST_FORMAT_BYTES:
Wim Taymans's avatar
Wim Taymans committed
385 386
          *dest_value = gst_util_uint64_scale_int (src_value,
              stream->strf.auds->av_bps, GST_SECOND);
387 388
          break;
        case GST_FORMAT_DEFAULT:
Wim Taymans's avatar
Wim Taymans committed
389 390
          *dest_value =
              gst_util_uint64_scale_round (src_value, stream->strh->rate,
391
              stream->strh->scale * GST_SECOND);
392 393 394 395
          break;
        default:
          res = FALSE;
          break;
396
      }
Ronald S. Bultje's avatar
Ronald S. Bultje committed
397
      break;
398 399
    case GST_FORMAT_BYTES:
      switch (*dest_format) {
400
        case GST_FORMAT_TIME:
401
          if (stream->strf.auds->av_bps != 0) {
402 403
            *dest_value = avi_stream_convert_bytes_to_time_unchecked (stream,
                src_value);
404 405
          } else
            res = FALSE;
406 407 408 409
          break;
        default:
          res = FALSE;
          break;
410 411
      }
      break;
412 413
    case GST_FORMAT_DEFAULT:
      switch (*dest_format) {
414
        case GST_FORMAT_TIME:
415 416
          *dest_value =
              avi_stream_convert_frames_to_time_unchecked (stream, src_value);
417 418 419 420
          break;
        default:
          res = FALSE;
          break;
421
      }
Wim Taymans's avatar
Wim Taymans committed
422 423
      break;
    default:
424
      res = FALSE;
425
  }
Andy Wingo's avatar
Andy Wingo committed
426

427
done:
428
  GST_LOG_OBJECT (pad,
429 430
      "Returning res:%d dest_format:%s dest_value:%" G_GUINT64_FORMAT, res,
      gst_format_get_name (*dest_format), *dest_value);
431
  return res;
432 433
}

434
static gboolean
Wim Taymans's avatar
Wim Taymans committed
435 436
gst_avi_demux_handle_src_query (GstPad * pad, GstObject * parent,
    GstQuery * query)
Andy Wingo's avatar
Andy Wingo committed
437
{
438
  gboolean res = TRUE;
Wim Taymans's avatar
Wim Taymans committed
439
  GstAviDemux *avi = GST_AVI_DEMUX (parent);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
440

Wim Taymans's avatar
Wim Taymans committed
441
  GstAviStream *stream = gst_pad_get_element_private (pad);
Wim Taymans's avatar
Wim Taymans committed
442

443
  if (!stream->strh || !stream->strf.data)
Wim Taymans's avatar
Wim Taymans committed
444
    return gst_pad_query_default (pad, parent, query);
445 446 447

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_POSITION:{
Wim Taymans's avatar
Wim Taymans committed
448
      gint64 pos = 0;
449

450
      GST_DEBUG ("pos query for stream %u: frames %u, bytes %u",
Wim Taymans's avatar
Wim Taymans committed
451
          stream->num, stream->current_entry, stream->current_total);
452

Wim Taymans's avatar
Wim Taymans committed
453
      /* FIXME, this looks clumsy */
454
      if (stream->strh->type == GST_RIFF_FCC_auds) {
455 456
        if (stream->is_vbr) {
          /* VBR */
Wim Taymans's avatar
Wim Taymans committed
457
          pos = gst_util_uint64_scale ((gint64) stream->current_entry *
458 459
              stream->strh->scale, GST_SECOND, (guint64) stream->strh->rate);
          GST_DEBUG_OBJECT (avi, "VBR convert frame %u, time %"
Wim Taymans's avatar
Wim Taymans committed
460
              GST_TIME_FORMAT, stream->current_entry, GST_TIME_ARGS (pos));
461
        } else if (stream->strf.auds->av_bps != 0) {
462
          /* CBR */
Wim Taymans's avatar
Wim Taymans committed
463
          pos = gst_util_uint64_scale (stream->current_total, GST_SECOND,
464 465
              (guint64) stream->strf.auds->av_bps);
          GST_DEBUG_OBJECT (avi,
466
              "CBR convert bytes %u, time %" GST_TIME_FORMAT,
Wim Taymans's avatar
Wim Taymans committed
467
              stream->current_total, GST_TIME_ARGS (pos));
Wim Taymans's avatar
Wim Taymans committed
468
        } else if (stream->idx_n != 0 && stream->total_bytes != 0) {
469
          /* calculate timestamps based on percentage of length */
470 471
          guint64 xlen = avi->avih->us_frame *
              avi->avih->tot_frames * GST_USECOND;
472

473
          if (stream->is_vbr) {
Wim Taymans's avatar
Wim Taymans committed
474
            pos = gst_util_uint64_scale (xlen, stream->current_entry,
Wim Taymans's avatar
Wim Taymans committed
475
                stream->idx_n);
476
            GST_DEBUG_OBJECT (avi, "VBR perc convert frame %u, time %"
Wim Taymans's avatar
Wim Taymans committed
477
                GST_TIME_FORMAT, stream->current_entry, GST_TIME_ARGS (pos));
478
          } else {
Wim Taymans's avatar
Wim Taymans committed
479
            pos = gst_util_uint64_scale (xlen, stream->current_total,
480
                stream->total_bytes);
481 482 483
            GST_DEBUG_OBJECT (avi,
                "CBR perc convert bytes %u, time %" GST_TIME_FORMAT,
                stream->current_total, GST_TIME_ARGS (pos));
484
          }
485
        } else {
486
          /* we don't know */
487
          res = FALSE;
488 489 490
        }
      } else {
        if (stream->strh->rate != 0) {
Wim Taymans's avatar
Wim Taymans committed
491
          pos = gst_util_uint64_scale ((guint64) stream->current_entry *
492
              stream->strh->scale, GST_SECOND, (guint64) stream->strh->rate);
493
        } else {
Wim Taymans's avatar
Wim Taymans committed
494
          pos = stream->current_entry * avi->avih->us_frame * GST_USECOND;
495
        }
Wim Taymans's avatar
Wim Taymans committed
496
      }
497 498
      if (res) {
        GST_DEBUG ("pos query : %" GST_TIME_FORMAT, GST_TIME_ARGS (pos));
Wim Taymans's avatar
Wim Taymans committed
499
        gst_query_set_position (query, GST_FORMAT_TIME, pos);
500 501
      } else
        GST_WARNING ("pos query failed");
Wim Taymans's avatar
Wim Taymans committed
502 503 504 505
      break;
    }
    case GST_QUERY_DURATION:
    {
506
      GstFormat fmt;
507
      GstClockTime duration;
508

509
      /* only act on audio or video streams */
510 511 512 513 514
      if (stream->strh->type != GST_RIFF_FCC_auds &&
          stream->strh->type != GST_RIFF_FCC_vids) {
        res = FALSE;
        break;
      }
515

516 517 518 519
      /* take stream duration, fall back to avih duration */
      if ((duration = stream->duration) == -1)
        duration = avi->duration;

520 521 522 523
      gst_query_parse_duration (query, &fmt, NULL);

      switch (fmt) {
        case GST_FORMAT_TIME:
524
          gst_query_set_duration (query, fmt, duration);
525 526
          break;
        case GST_FORMAT_DEFAULT:
527 528
        {
          gint64 dur;
529
          GST_DEBUG_OBJECT (query, "total frames is %" G_GUINT32_FORMAT,
Wim Taymans's avatar
Wim Taymans committed
530
              stream->idx_n);
531

532
          if (stream->idx_n > 0)
Wim Taymans's avatar
Wim Taymans committed
533
            gst_query_set_duration (query, fmt, stream->idx_n);
534
          else if (gst_pad_query_convert (pad, GST_FORMAT_TIME,
535
                  duration, fmt, &dur))
536
            gst_query_set_duration (query, fmt, dur);
537
          break;
538
        }
539 540 541 542
        default:
          res = FALSE;
          break;
      }
Wim Taymans's avatar
Wim Taymans committed
543
      break;
544
    }
545 546 547 548 549 550 551 552
    case GST_QUERY_SEEKING:{
      GstFormat fmt;

      gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL);
      if (fmt == GST_FORMAT_TIME) {
        gboolean seekable = TRUE;

        if (avi->streaming) {
553
          seekable = avi->seekable;
554 555 556 557 558 559 560 561
        }

        gst_query_set_seeking (query, GST_FORMAT_TIME, seekable,
            0, stream->duration);
        res = TRUE;
      }
      break;
    }
562 563 564 565 566 567 568 569 570
    case GST_QUERY_CONVERT:{
      GstFormat src_fmt, dest_fmt;
      gint64 src_val, dest_val;

      gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
      if ((res = gst_avi_demux_src_convert (pad, src_fmt, src_val, &dest_fmt,
                  &dest_val)))
        gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
      else
Wim Taymans's avatar
Wim Taymans committed
571
        res = gst_pad_query_default (pad, parent, query);
572 573
      break;
    }
Wim Taymans's avatar
Wim Taymans committed
574
    default:
Wim Taymans's avatar
Wim Taymans committed
575
      res = gst_pad_query_default (pad, parent, query);
Wim Taymans's avatar
Wim Taymans committed
576 577 578 579 580 581
      break;
  }

  return res;
}

582
#if 0
583
static const GstEventMask *
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
584
gst_avi_demux_get_event_mask (GstPad * pad)
585 586
{
  static const GstEventMask masks[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
587 588
    {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_KEY_UNIT},
    {0,}
589 590 591 592
  };

  return masks;
}
593
#endif
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
594

595
#if 0
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
static guint64
gst_avi_demux_seek_streams (GstAviDemux * avi, guint64 offset, gboolean before)
{
  GstAviStream *stream;
  GstIndexEntry *entry;
  gint i;
  gint64 val, min = offset;

  for (i = 0; i < avi->num_streams; i++) {
    stream = &avi->stream[i];

    entry = gst_index_get_assoc_entry (avi->element_index, stream->index_id,
        before ? GST_INDEX_LOOKUP_BEFORE : GST_INDEX_LOOKUP_AFTER,
        GST_ASSOCIATION_FLAG_NONE, GST_FORMAT_BYTES, offset);

    if (before) {
      if (entry) {
613
        gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &val);
614 615 616 617 618 619 620 621 622
        GST_DEBUG_OBJECT (avi, "stream %d, previous entry at %"
            G_GUINT64_FORMAT, i, val);
        if (val < min)
          min = val;
      }
      continue;
    }

    if (!entry) {
623
      GST_DEBUG_OBJECT (avi, "no position for stream %d, assuming at start", i);
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
      stream->current_entry = 0;
      stream->current_total = 0;
      continue;
    }

    gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &val);
    GST_DEBUG_OBJECT (avi, "stream %d, next entry at %" G_GUINT64_FORMAT,
        i, val);

    gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &val);
    stream->current_total = val;
    gst_index_entry_assoc_map (entry, GST_FORMAT_DEFAULT, &val);
    stream->current_entry = val;
  }

  return min;
}
641
#endif
642

643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
static guint
gst_avi_demux_index_entry_offset_search (GstAviIndexEntry * entry,
    guint64 * offset)
{
  if (entry->offset < *offset)
    return -1;
  else if (entry->offset > *offset)
    return 1;
  return 0;
}

static guint64
gst_avi_demux_seek_streams_index (GstAviDemux * avi, guint64 offset,
    gboolean before)
{
  GstAviStream *stream;
  GstAviIndexEntry *entry;
  gint i;
  gint64 val, min = offset;
662
  guint index = 0;
663 664 665 666

  for (i = 0; i < avi->num_streams; i++) {
    stream = &avi->stream[i];

667 668
    /* compensate for chunk header */
    offset += 8;
669 670 671 672 673
    entry =
        gst_util_array_binary_search (stream->index, stream->idx_n,
        sizeof (GstAviIndexEntry),
        (GCompareDataFunc) gst_avi_demux_index_entry_offset_search,
        before ? GST_SEARCH_MODE_BEFORE : GST_SEARCH_MODE_AFTER, &offset, NULL);
674
    offset -= 8;
675 676 677 678 679 680

    if (entry)
      index = entry - stream->index;

    if (before) {
      if (entry) {
681
        val = stream->index[index].offset;
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
        GST_DEBUG_OBJECT (avi,
            "stream %d, previous entry at %" G_GUINT64_FORMAT, i, val);
        if (val < min)
          min = val;
      }
      continue;
    }

    if (!entry) {
      GST_DEBUG_OBJECT (avi, "no position for stream %d, assuming at start", i);
      stream->current_entry = 0;
      stream->current_total = 0;
      continue;
    }

697
    val = stream->index[index].offset - 8;
698 699 700
    GST_DEBUG_OBJECT (avi, "stream %d, next entry at %" G_GUINT64_FORMAT, i,
        val);

701
    stream->current_total = stream->index[index].total;
702 703 704 705 706 707
    stream->current_entry = index;
  }

  return min;
}

708 709
#define GST_AVI_SEEK_PUSH_DISPLACE     (4 * GST_SECOND)

710
static gboolean
Wim Taymans's avatar
Wim Taymans committed
711 712
gst_avi_demux_handle_sink_event (GstPad * pad, GstObject * parent,
    GstEvent * event)
713 714
{
  gboolean res = TRUE;
Wim Taymans's avatar
Wim Taymans committed
715
  GstAviDemux *avi = GST_AVI_DEMUX (parent);
716 717 718 719 720

  GST_DEBUG_OBJECT (avi,
      "have event type %s: %p on sink pad", GST_EVENT_TYPE_NAME (event), event);

  switch (GST_EVENT_TYPE (event)) {
721
    case GST_EVENT_SEGMENT:
722
    {
723
      gint64 boffset, offset = 0;
724 725 726
      GstSegment segment;

      /* some debug output */
727 728
      gst_event_copy_segment (event, &segment);
      GST_DEBUG_OBJECT (avi, "received newsegment %" GST_SEGMENT_FORMAT,
729 730 731 732 733 734 735 736 737
          &segment);

      /* chain will send initial newsegment after pads have been added */
      if (avi->state != GST_AVI_DEMUX_MOVI) {
        GST_DEBUG_OBJECT (avi, "still starting, eating event");
        goto exit;
      }

      /* we only expect a BYTE segment, e.g. following a seek */
738
      if (segment.format != GST_FORMAT_BYTES) {
739 740 741 742
        GST_DEBUG_OBJECT (avi, "unsupported segment format, ignoring");
        goto exit;
      }

743 744
      if (avi->have_index) {
        GstAviIndexEntry *entry;
745
        guint i = 0, index = 0, k = 0;
746 747
        GstAviStream *stream;

748
        /* compensate chunk header, stored index offset points after header */
749
        boffset = segment.start + 8;
750 751 752 753 754 755 756 757
        /* find which stream we're on */
        do {
          stream = &avi->stream[i];

          /* find the index for start bytes offset */
          entry = gst_util_array_binary_search (stream->index,
              stream->idx_n, sizeof (GstAviIndexEntry),
              (GCompareDataFunc) gst_avi_demux_index_entry_offset_search,
758
              GST_SEARCH_MODE_AFTER, &boffset, NULL);
759

760 761 762
          if (entry == NULL)
            continue;
          index = entry - stream->index;
763

764 765 766 767 768 769
          /* we are on the stream with a chunk start offset closest to start */
          if (!offset || stream->index[index].offset < offset) {
            offset = stream->index[index].offset;
            k = i;
          }
          /* exact match needs no further searching */
770
          if (stream->index[index].offset == segment.start)
771 772
            break;
        } while (++i < avi->num_streams);
773
        boffset -= 8;
774 775 776 777 778 779 780 781
        offset -= 8;
        stream = &avi->stream[k];

        /* so we have no idea what is to come, or where we are */
        if (!offset) {
          GST_WARNING_OBJECT (avi, "insufficient index data, forcing EOS");
          goto eos;
        }
782

783
        /* get the ts corresponding to start offset bytes for the stream */
784
        gst_avi_demux_get_buffer_info (avi, stream, index,
785
            (GstClockTime *) & segment.time, NULL, NULL, NULL);
786
#if 0
787 788 789 790 791 792
      } else if (avi->element_index) {
        GstIndexEntry *entry;

        /* Let's check if we have an index entry for this position */
        entry = gst_index_get_assoc_entry (avi->element_index, avi->index_id,
            GST_INDEX_LOOKUP_AFTER, GST_ASSOCIATION_FLAG_NONE,
793
            GST_FORMAT_BYTES, segment.start);
794 795 796 797 798 799

        /* we can not go where we have not yet been before ... */
        if (!entry) {
          GST_WARNING_OBJECT (avi, "insufficient index data, forcing EOS");
          goto eos;
        }
800

801 802
        gst_index_entry_assoc_map (entry, GST_FORMAT_TIME,
            (gint64 *) & segment.time);
803
        gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &offset);
804
#endif
805 806
      } else {
        GST_WARNING_OBJECT (avi, "no index data, forcing EOS");
807 808 809
        goto eos;
      }

810 811 812
      segment.format = GST_FORMAT_TIME;
      segment.start = segment.time;
      segment.stop = GST_CLOCK_TIME_NONE;
813 814 815 816
      segment.position = segment.start;

      /* rescue duration */
      segment.duration = avi->segment.duration;
817 818

      /* set up segment and send downstream */
819 820 821 822 823 824 825
      gst_segment_copy_into (&segment, &avi->segment);

      GST_DEBUG_OBJECT (avi, "Pushing newseg %" GST_SEGMENT_FORMAT, &segment);
      gst_avi_demux_push_event (avi, gst_event_new_segment (&segment));

      GST_DEBUG_OBJECT (avi, "next chunk expected at %" G_GINT64_FORMAT,
          boffset);
826 827

      /* adjust state for streaming thread accordingly */
828 829
      if (avi->have_index)
        gst_avi_demux_seek_streams_index (avi, offset, FALSE);
830
#if 0
831 832
      else
        gst_avi_demux_seek_streams (avi, offset, FALSE);
833
#endif
834 835

      /* set up streaming thread */
836 837 838
      g_assert (offset >= boffset);
      avi->offset = boffset;
      avi->todrop = offset - boffset;
839 840

    exit:
841
      gst_event_unref (event);
842
      res = TRUE;
843
      break;
844 845 846 847 848
    eos:
      /* set up for EOS */
      avi->have_eos = TRUE;
      goto exit;
    }
849 850 851 852 853 854 855 856 857 858 859 860
    case GST_EVENT_EOS:
    {
      if (avi->state != GST_AVI_DEMUX_MOVI) {
        gst_event_unref (event);
        GST_ELEMENT_ERROR (avi, STREAM, DEMUX,
            (NULL), ("got eos and didn't receive a complete header object"));
      } else if (!gst_avi_demux_push_event (avi, event)) {
        GST_ELEMENT_ERROR (avi, STREAM, DEMUX,
            (NULL), ("got eos but no streams (yet)"));
      }
      break;
    }
861 862 863 864 865 866 867 868 869 870 871 872
    case GST_EVENT_FLUSH_STOP:
    {
      gint i;

      gst_adapter_clear (avi->adapter);
      avi->have_eos = FALSE;
      for (i = 0; i < avi->num_streams; i++) {
        avi->stream[i].last_flow = GST_FLOW_OK;
        avi->stream[i].discont = TRUE;
      }
      /* fall through to default case so that the event gets passed downstream */
    }
873
    default:
Wim Taymans's avatar
Wim Taymans committed
874
      res = gst_pad_event_default (pad, parent, event);
875 876 877 878 879 880
      break;
  }

  return res;
}

Wim Taymans's avatar
Wim Taymans committed
881
static gboolean
Wim Taymans's avatar
Wim Taymans committed
882 883
gst_avi_demux_handle_src_event (GstPad * pad, GstObject * parent,
    GstEvent * event)
Wim Taymans's avatar
Wim Taymans committed
884 885
{
  gboolean res = TRUE;
Wim Taymans's avatar
Wim Taymans committed
886
  GstAviDemux *avi = GST_AVI_DEMUX (parent);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
887

888
  GST_DEBUG_OBJECT (avi,
889
      "have event type %s: %p on src pad", GST_EVENT_TYPE_NAME (event), event);
Wim Taymans's avatar
Wim Taymans committed
890 891 892

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
893 894 895
      if (!avi->streaming) {
        res = gst_avi_demux_handle_seek (avi, pad, event);
      } else {
896
        res = gst_avi_demux_handle_seek_push (avi, pad, event);
897
      }
898
      gst_event_unref (event);
899 900 901 902 903
      break;
    case GST_EVENT_QOS:
    case GST_EVENT_NAVIGATION:
      res = FALSE;
      gst_event_unref (event);
Wim Taymans's avatar
Wim Taymans committed
904 905
      break;
    default:
Wim Taymans's avatar
Wim Taymans committed
906
      res = gst_pad_event_default (pad, parent, event);
Wim Taymans's avatar
Wim Taymans committed
907 908
      break;
  }
909

Wim Taymans's avatar
Wim Taymans committed
910 911 912
  return res;
}

913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
/* streaming helper (push) */

/*
 * gst_avi_demux_peek_chunk_info:
 * @avi: Avi object
 * @tag: holder for tag
 * @size: holder for tag size
 *
 * Peek next chunk info (tag and size)
 *
 * Returns: TRUE when one chunk info has been got
 */
static gboolean
gst_avi_demux_peek_chunk_info (GstAviDemux * avi, guint32 * tag, guint32 * size)
{
  const guint8 *data = NULL;

Wim Taymans's avatar
Wim Taymans committed
930
  if (gst_adapter_available (avi->adapter) < 8)
931 932
    return FALSE;

Wim Taymans's avatar
Wim Taymans committed
933
  data = gst_adapter_map (avi->adapter, 8);
934 935
  *tag = GST_READ_UINT32_LE (data);
  *size = GST_READ_UINT32_LE (data + 4);
Wim Taymans's avatar
Wim Taymans committed
936
  gst_adapter_unmap (avi->adapter);
937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954

  return TRUE;
}

/*
 * gst_avi_demux_peek_chunk:
 * @avi: Avi object
 * @tag: holder for tag
 * @size: holder for tag size
 *
 * Peek enough data for one full chunk
 *
 * Returns: %TRUE when one chunk has been got
 */
static gboolean
gst_avi_demux_peek_chunk (GstAviDemux * avi, guint32 * tag, guint32 * size)
{
  guint32 peek_size = 0;
955
  gint available;
956

Wim Taymans's avatar
Wim Taymans committed
957 958
  if (!gst_avi_demux_peek_chunk_info (avi, tag, size))
    goto peek_failed;
959 960 961 962 963

  /* size 0 -> empty data buffer would surprise most callers,
   * large size -> do not bother trying to squeeze that into adapter,
   * so we throw poor man's exception, which can be caught if caller really
   * wants to handle 0 size chunk */
Wim Taymans's avatar
Wim Taymans committed
964 965 966
  if (!(*size) || (*size) >= (1 << 30))
    goto strange_size;

967
  peek_size = (*size + 1) & ~1;
968
  available = gst_adapter_available (avi->adapter);
969

Wim Taymans's avatar
Wim Taymans committed
970 971
  GST_DEBUG_OBJECT (avi,
      "Need to peek chunk of %d bytes to read chunk %" GST_FOURCC_FORMAT
972 973
      ", %d bytes available", *size, GST_FOURCC_ARGS (*tag), available);

Wim Taymans's avatar
Wim Taymans committed
974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997
  if (available < (8 + peek_size))
    goto need_more;

  return TRUE;

  /* ERRORS */
peek_failed:
  {
    GST_INFO_OBJECT (avi, "Failed to peek");
    return FALSE;
  }
strange_size:
  {
    GST_INFO_OBJECT (avi,
        "Invalid/unexpected chunk size %d for tag %" GST_FOURCC_FORMAT, *size,
        GST_FOURCC_ARGS (*tag));
    /* chain should give up */
    avi->abort_buffering = TRUE;
    return FALSE;
  }
need_more:
  {
    GST_INFO_OBJECT (avi, "need more %d < %" G_GUINT32_FORMAT,
        available, 8 + peek_size);
998 999 1000 1001 1002 1003 1004
    return FALSE;
  }
}

/* AVI init */

/*
1005 1006 1007 1008 1009
 * gst_avi_demux_parse_file_header:
 * @element: caller element (used for errors/debug).
 * @buf: input data to be used for parsing.
 *
 * "Open" a RIFF/AVI file. The buffer should be at least 12
1010
 * bytes long. Takes ownership of @buf.
1011 1012 1013
 *
 * Returns: TRUE if the file is a RIFF/AVI file, FALSE otherwise.
 *          Throws an error, caller should error out (fatal).
1014
 */
1015
static gboolean
1016
gst_avi_demux_parse_file_header (GstElement * element, GstBuffer * buf)
Andy Wingo's avatar
Andy Wingo committed
1017
{
1018
  guint32 doctype;
1019 1020 1021
  GstClockTime stamp;

  stamp = gst_util_get_timestamp ();
Andy Wingo's avatar
Andy Wingo committed
1022

1023
  /* riff_parse posts an error */
1024
  if (!gst_riff_parse_file_header (element, buf, &doctype))
1025
    return FALSE;
1026

1027 1028 1029
  if (doctype != GST_RIFF_RIFF_AVI)
    goto not_avi;

Wim Taymans's avatar