gstavidemux.c 158 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
 *
Wim Taymans's avatar
Wim Taymans committed
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
 * |[
Wim Taymans's avatar
Wim Taymans committed
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>
 *
Wim Taymans's avatar
Wim Taymans committed
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
static void gst_avi_demux_base_init (GstAviDemuxClass * klass);
static void gst_avi_demux_class_init (GstAviDemuxClass * klass);
static void gst_avi_demux_init (GstAviDemux * avi);
78
static void gst_avi_demux_finalize (GObject * object);
79

80
static void gst_avi_demux_reset (GstAviDemux * avi);
81

82
#if 0
83
static const GstEventMask *gst_avi_demux_get_event_mask (GstPad * pad);
84
#endif
85
static gboolean gst_avi_demux_handle_src_event (GstPad * pad, GstEvent * event);
86
87
static gboolean gst_avi_demux_handle_sink_event (GstPad * pad,
    GstEvent * event);
88
static gboolean gst_avi_demux_push_event (GstAviDemux * avi, GstEvent * event);
89
90

#if 0
91
static const GstFormat *gst_avi_demux_get_src_formats (GstPad * pad);
92
#endif
93
94
95
96
97
static const GstQueryType *gst_avi_demux_get_src_query_types (GstPad * pad);
static gboolean gst_avi_demux_handle_src_query (GstPad * pad, GstQuery * query);
static gboolean gst_avi_demux_src_convert (GstPad * pad, GstFormat src_format,
    gint64 src_value, GstFormat * dest_format, gint64 * dest_value);

98
99
100
static gboolean gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment);
static gboolean gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad,
    GstEvent * event);
101
102
static gboolean gst_avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad,
    GstEvent * event);
103
104
105
static void gst_avi_demux_loop (GstPad * pad);
static gboolean gst_avi_demux_sink_activate (GstPad * sinkpad);
static gboolean gst_avi_demux_sink_activate_pull (GstPad * sinkpad,
106
    gboolean active);
107
108
109
static gboolean gst_avi_demux_activate_push (GstPad * pad, gboolean active);
static GstFlowReturn gst_avi_demux_chain (GstPad * pad, GstBuffer * buf);

110
111
static void gst_avi_demux_set_index (GstElement * element, GstIndex * index);
static GstIndex *gst_avi_demux_get_index (GstElement * element);
112
113
static GstStateChangeReturn gst_avi_demux_change_state (GstElement * element,
    GstStateChange transition);
114
115
116
117
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
118

119
120
static void gst_avi_demux_parse_idit (GstAviDemux * avi, GstBuffer * buf);

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

123
/* GObject methods */
124

Andy Wingo's avatar
Andy Wingo committed
125
GType
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
126
gst_avi_demux_get_type (void)
Andy Wingo's avatar
Andy Wingo committed
127
128
129
130
131
{
  static GType avi_demux_type = 0;

  if (!avi_demux_type) {
    static const GTypeInfo avi_demux_info = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
132
      sizeof (GstAviDemuxClass),
133
      (GBaseInitFunc) gst_avi_demux_base_init,
Andy Wingo's avatar
Andy Wingo committed
134
      NULL,
135
      (GClassInitFunc) gst_avi_demux_class_init,
Andy Wingo's avatar
Andy Wingo committed
136
137
      NULL,
      NULL,
138
      sizeof (GstAviDemux),
Andy Wingo's avatar
Andy Wingo committed
139
      0,
140
      (GInstanceInitFunc) gst_avi_demux_init,
Andy Wingo's avatar
Andy Wingo committed
141
    };
142
143

    avi_demux_type =
144
        g_type_register_static (GST_TYPE_ELEMENT,
145
        "GstAviDemux", &avi_demux_info, 0);
Andy Wingo's avatar
Andy Wingo committed
146
  }
147

Andy Wingo's avatar
Andy Wingo committed
148
149
150
  return avi_demux_type;
}

151
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
152
gst_avi_demux_base_init (GstAviDemuxClass * klass)
153
{
154
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
155
156
  GstPadTemplate *videosrctempl, *audiosrctempl, *subsrctempl;
  GstCaps *audcaps, *vidcaps, *subcaps;
157

158
  audcaps = gst_riff_create_audio_template_caps ();
159
  gst_caps_append (audcaps, gst_caps_new_simple ("audio/x-avi-unknown", NULL));
160
  audiosrctempl = gst_pad_template_new ("audio_%02d",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
161
      GST_PAD_SRC, GST_PAD_SOMETIMES, audcaps);
162

David Schleef's avatar
David Schleef committed
163
164
  vidcaps = gst_riff_create_video_template_caps ();
  gst_caps_append (vidcaps, gst_riff_create_iavs_template_caps ());
165
  gst_caps_append (vidcaps, gst_caps_new_simple ("video/x-avi-unknown", NULL));
166
  videosrctempl = gst_pad_template_new ("video_%02d",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
167
      GST_PAD_SRC, GST_PAD_SOMETIMES, vidcaps);
168

169
170
171
  subcaps = gst_caps_new_simple ("application/x-subtitle-avi", NULL);
  subsrctempl = gst_pad_template_new ("subtitle_%02d",
      GST_PAD_SRC, GST_PAD_SOMETIMES, subcaps);
172
173
  gst_element_class_add_pad_template (element_class, audiosrctempl);
  gst_element_class_add_pad_template (element_class, videosrctempl);
174
  gst_element_class_add_pad_template (element_class, subsrctempl);
175
  gst_element_class_add_pad_template (element_class,
David Schleef's avatar
David Schleef committed
176
      gst_static_pad_template_get (&sink_templ));
177
178
179
180
181
182
  gst_element_class_set_details_simple (element_class, "Avi demuxer",
      "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>");
183
184
}

Andy Wingo's avatar
Andy Wingo committed
185
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
186
gst_avi_demux_class_init (GstAviDemuxClass * klass)
Andy Wingo's avatar
Andy Wingo committed
187
{
188
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
189
  GObjectClass *gobject_class = (GObjectClass *) klass;
Andy Wingo's avatar
Andy Wingo committed
190

191
  GST_DEBUG_CATEGORY_INIT (avidemux_debug, "avidemux",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
192
      0, "Demuxer for AVI streams");
193

194
  parent_class = g_type_class_peek_parent (klass);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
195

196
  gobject_class->finalize = gst_avi_demux_finalize;
197
198
  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_avi_demux_change_state);
199
200
201

  gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_avi_demux_set_index);
  gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_avi_demux_get_index);
Andy Wingo's avatar
Andy Wingo committed
202
203
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
204
205
static void
gst_avi_demux_init (GstAviDemux * avi)
206
{
207
  avi->sinkpad = gst_pad_new_from_static_template (&sink_templ, "sink");
208
209
  gst_pad_set_activate_function (avi->sinkpad,
      GST_DEBUG_FUNCPTR (gst_avi_demux_sink_activate));
210
  gst_pad_set_activatepull_function (avi->sinkpad,
211
212
213
214
215
216
217
      GST_DEBUG_FUNCPTR (gst_avi_demux_sink_activate_pull));
  gst_pad_set_activatepush_function (avi->sinkpad,
      GST_DEBUG_FUNCPTR (gst_avi_demux_activate_push));
  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));
Wim Taymans's avatar
Wim Taymans committed
218
  gst_element_add_pad (GST_ELEMENT_CAST (avi), avi->sinkpad);
Wim Taymans's avatar
Wim Taymans committed
219

220
  avi->adapter = gst_adapter_new ();
Wim Taymans's avatar
Wim Taymans committed
221

222
  gst_avi_demux_reset (avi);
Wim Taymans's avatar
Wim Taymans committed
223
224
}

225
static void
226
gst_avi_demux_finalize (GObject * object)
227
228
229
{
  GstAviDemux *avi = GST_AVI_DEMUX (object);

230
231
232
  GST_DEBUG ("AVI: finalize");

  g_object_unref (avi->adapter);
233

234
  G_OBJECT_CLASS (parent_class)->finalize (object);
235
236
}

Wim Taymans's avatar
Wim Taymans committed
237
238
239
240
241
242
243
244
245
246
247
248
249
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) {
250
251
    if (stream->exposed) {
      gst_pad_set_active (stream->pad, FALSE);
Wim Taymans's avatar
Wim Taymans committed
252
      gst_element_remove_pad (GST_ELEMENT_CAST (avi), stream->pad);
253
254
    } else
      gst_object_unref (stream->pad);
Wim Taymans's avatar
Wim Taymans committed
255
256
257
258
259
260
261
262
  }
  if (stream->taglist) {
    gst_tag_list_free (stream->taglist);
    stream->taglist = NULL;
  }
  memset (stream, 0, sizeof (GstAviStream));
}

263
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
264
gst_avi_demux_reset (GstAviDemux * avi)
265
{
266
  gint i;
267

268
269
  GST_DEBUG ("AVI: reset");

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

273
  avi->header_state = GST_AVI_DEMUX_HEADER_TAG_LIST;
274
275
276
  avi->num_streams = 0;
  avi->num_v_streams = 0;
  avi->num_a_streams = 0;
277
  avi->num_t_streams = 0;
278
  avi->main_stream = -1;
279

280
  avi->state = GST_AVI_DEMUX_START;
281
  avi->offset = 0;
Wim Taymans's avatar
Wim Taymans committed
282
  avi->building_index = FALSE;
283

284
  avi->index_offset = 0;
285
286
  g_free (avi->avih);
  avi->avih = NULL;
287

288
289
290
291
  if (avi->element_index)
    gst_object_unref (avi->element_index);
  avi->element_index = NULL;

Wim Taymans's avatar
Wim Taymans committed
292
293
294
  if (avi->seg_event) {
    gst_event_unref (avi->seg_event);
    avi->seg_event = NULL;
295
  }
Wim Taymans's avatar
Wim Taymans committed
296
297
298
299
  if (avi->seek_event) {
    gst_event_unref (avi->seek_event);
    avi->seek_event = NULL;
  }
300

301
302
303
304
  if (avi->globaltags)
    gst_tag_list_free (avi->globaltags);
  avi->globaltags = NULL;

305
  avi->got_tags = TRUE;         /* we always want to push global tags */
306
  avi->have_eos = FALSE;
307
  avi->seekable = TRUE;
308

309
310
  gst_adapter_clear (avi->adapter);

311
  gst_segment_init (&avi->segment, GST_FORMAT_TIME);
312
313
}

314

315
316
/* GstElement methods */

317
#if 0
318
static const GstFormat *
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
319
gst_avi_demux_get_src_formats (GstPad * pad)
Andy Wingo's avatar
Andy Wingo committed
320
{
Wim Taymans's avatar
Wim Taymans committed
321
  GstAviStream *stream = gst_pad_get_element_private (pad);
Andy Wingo's avatar
Andy Wingo committed
322

323
324
325
326
327
328
329
330
331
332
333
  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
334

335
  return (stream->strh->type == GST_RIFF_FCC_auds ?
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
336
      src_a_formats : src_v_formats);
337
}
338
#endif
339

340
341
/* assumes stream->strf.auds->av_bps != 0 */
static inline GstClockTime
Wim Taymans's avatar
Wim Taymans committed
342
avi_stream_convert_bytes_to_time_unchecked (GstAviStream * stream,
343
344
    guint64 bytes)
{
Wim Taymans's avatar
Wim Taymans committed
345
346
  return gst_util_uint64_scale_int (bytes, GST_SECOND,
      stream->strf.auds->av_bps);
347
348
}

Wim Taymans's avatar
Wim Taymans committed
349
350
351
352
static inline guint64
avi_stream_convert_time_to_bytes_unchecked (GstAviStream * stream,
    GstClockTime time)
{
Wim Taymans's avatar
Wim Taymans committed
353
354
  return gst_util_uint64_scale_int (time, stream->strf.auds->av_bps,
      GST_SECOND);
Wim Taymans's avatar
Wim Taymans committed
355
356
}

357
358
/* assumes stream->strh->rate != 0 */
static inline GstClockTime
Wim Taymans's avatar
Wim Taymans committed
359
avi_stream_convert_frames_to_time_unchecked (GstAviStream * stream,
360
361
362
363
364
365
    guint64 frames)
{
  return gst_util_uint64_scale (frames, stream->strh->scale * GST_SECOND,
      stream->strh->rate);
}

Wim Taymans's avatar
Wim Taymans committed
366
367
368
369
370
371
372
373
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);
}

374
static gboolean
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
375
376
377
gst_avi_demux_src_convert (GstPad * pad,
    GstFormat src_format,
    gint64 src_value, GstFormat * dest_format, gint64 * dest_value)
378
{
Wim Taymans's avatar
Wim Taymans committed
379
  GstAviStream *stream = gst_pad_get_element_private (pad);
380
  gboolean res = TRUE;
381

382
  GST_LOG_OBJECT (pad,
383
384
385
      "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));
386

387
  if (G_UNLIKELY (src_format == *dest_format)) {
388
    *dest_value = src_value;
389
390
    goto done;
  }
391
  if (G_UNLIKELY (!stream->strh || !stream->strf.data)) {
392
393
    res = FALSE;
    goto done;
394
  }
395
396
397
  if (G_UNLIKELY (stream->strh->type == GST_RIFF_FCC_vids &&
          (src_format == GST_FORMAT_BYTES
              || *dest_format == GST_FORMAT_BYTES))) {
398
399
400
    res = FALSE;
    goto done;
  }
401

402
403
404
  switch (src_format) {
    case GST_FORMAT_TIME:
      switch (*dest_format) {
405
        case GST_FORMAT_BYTES:
Wim Taymans's avatar
Wim Taymans committed
406
407
          *dest_value = gst_util_uint64_scale_int (src_value,
              stream->strf.auds->av_bps, GST_SECOND);
408
409
          break;
        case GST_FORMAT_DEFAULT:
Wim Taymans's avatar
Wim Taymans committed
410
411
          *dest_value =
              gst_util_uint64_scale_round (src_value, stream->strh->rate,
412
              stream->strh->scale * GST_SECOND);
413
414
415
416
          break;
        default:
          res = FALSE;
          break;
417
      }
Ronald S. Bultje's avatar
Ronald S. Bultje committed
418
      break;
419
420
    case GST_FORMAT_BYTES:
      switch (*dest_format) {
421
        case GST_FORMAT_TIME:
422
          if (stream->strf.auds->av_bps != 0) {
423
424
            *dest_value = avi_stream_convert_bytes_to_time_unchecked (stream,
                src_value);
425
426
          } else
            res = FALSE;
427
428
429
430
          break;
        default:
          res = FALSE;
          break;
431
432
      }
      break;
433
434
    case GST_FORMAT_DEFAULT:
      switch (*dest_format) {
435
        case GST_FORMAT_TIME:
436
437
          *dest_value =
              avi_stream_convert_frames_to_time_unchecked (stream, src_value);
438
439
440
441
          break;
        default:
          res = FALSE;
          break;
442
      }
Wim Taymans's avatar
Wim Taymans committed
443
444
      break;
    default:
445
      res = FALSE;
446
  }
Andy Wingo's avatar
Andy Wingo committed
447

448
done:
449
  GST_LOG_OBJECT (pad,
450
451
      "Returning res:%d dest_format:%s dest_value:%" G_GUINT64_FORMAT, res,
      gst_format_get_name (*dest_format), *dest_value);
452
  return res;
453
454
}

455
static const GstQueryType *
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
456
gst_avi_demux_get_src_query_types (GstPad * pad)
Andy Wingo's avatar
Andy Wingo committed
457
{
458
459
  static const GstQueryType src_types[] = {
    GST_QUERY_POSITION,
460
    GST_QUERY_DURATION,
461
    GST_QUERY_SEEKING,
462
    GST_QUERY_CONVERT,
463
464
    0
  };
Andy Wingo's avatar
Andy Wingo committed
465

466
  return src_types;
Wim Taymans's avatar
Wim Taymans committed
467
468
}

469
static gboolean
470
gst_avi_demux_handle_src_query (GstPad * pad, GstQuery * query)
Andy Wingo's avatar
Andy Wingo committed
471
{
472
  gboolean res = TRUE;
473
  GstAviDemux *avi = GST_AVI_DEMUX (gst_pad_get_parent (pad));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
474

Wim Taymans's avatar
Wim Taymans committed
475
  GstAviStream *stream = gst_pad_get_element_private (pad);
Wim Taymans's avatar
Wim Taymans committed
476

477
  if (!stream->strh || !stream->strf.data)
478
    return gst_pad_query_default (pad, query);
479
480
481

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

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

Wim Taymans's avatar
Wim Taymans committed
487
      /* FIXME, this looks clumsy */
488
      if (stream->strh->type == GST_RIFF_FCC_auds) {
489
490
        if (stream->is_vbr) {
          /* VBR */
Wim Taymans's avatar
Wim Taymans committed
491
          pos = gst_util_uint64_scale ((gint64) stream->current_entry *
492
493
              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
494
              GST_TIME_FORMAT, stream->current_entry, GST_TIME_ARGS (pos));
495
        } else if (stream->strf.auds->av_bps != 0) {
496
          /* CBR */
Wim Taymans's avatar
Wim Taymans committed
497
          pos = gst_util_uint64_scale (stream->current_total, GST_SECOND,
498
499
              (guint64) stream->strf.auds->av_bps);
          GST_DEBUG_OBJECT (avi,
500
              "CBR convert bytes %u, time %" GST_TIME_FORMAT,
Wim Taymans's avatar
Wim Taymans committed
501
              stream->current_total, GST_TIME_ARGS (pos));
Wim Taymans's avatar
Wim Taymans committed
502
        } else if (stream->idx_n != 0 && stream->total_bytes != 0) {
503
          /* calculate timestamps based on percentage of length */
504
505
          guint64 xlen = avi->avih->us_frame *
              avi->avih->tot_frames * GST_USECOND;
506

507
          if (stream->is_vbr) {
Wim Taymans's avatar
Wim Taymans committed
508
            pos = gst_util_uint64_scale (xlen, stream->current_entry,
Wim Taymans's avatar
Wim Taymans committed
509
                stream->idx_n);
510
            GST_DEBUG_OBJECT (avi, "VBR perc convert frame %u, time %"
Wim Taymans's avatar
Wim Taymans committed
511
                GST_TIME_FORMAT, stream->current_entry, GST_TIME_ARGS (pos));
512
          } else {
Wim Taymans's avatar
Wim Taymans committed
513
            pos = gst_util_uint64_scale (xlen, stream->current_total,
514
                stream->total_bytes);
515
516
517
            GST_DEBUG_OBJECT (avi,
                "CBR perc convert bytes %u, time %" GST_TIME_FORMAT,
                stream->current_total, GST_TIME_ARGS (pos));
518
          }
519
        } else {
520
          /* we don't know */
521
          res = FALSE;
522
523
524
        }
      } else {
        if (stream->strh->rate != 0) {
Wim Taymans's avatar
Wim Taymans committed
525
          pos = gst_util_uint64_scale ((guint64) stream->current_entry *
526
              stream->strh->scale, GST_SECOND, (guint64) stream->strh->rate);
527
        } else {
Wim Taymans's avatar
Wim Taymans committed
528
          pos = stream->current_entry * avi->avih->us_frame * GST_USECOND;
529
        }
Wim Taymans's avatar
Wim Taymans committed
530
      }
531
532
      if (res) {
        GST_DEBUG ("pos query : %" GST_TIME_FORMAT, GST_TIME_ARGS (pos));
Wim Taymans's avatar
Wim Taymans committed
533
        gst_query_set_position (query, GST_FORMAT_TIME, pos);
534
535
      } else
        GST_WARNING ("pos query failed");
Wim Taymans's avatar
Wim Taymans committed
536
537
538
539
      break;
    }
    case GST_QUERY_DURATION:
    {
540
      GstFormat fmt;
541
      GstClockTime duration;
542

543
      /* only act on audio or video streams */
544
545
546
547
548
      if (stream->strh->type != GST_RIFF_FCC_auds &&
          stream->strh->type != GST_RIFF_FCC_vids) {
        res = FALSE;
        break;
      }
549

550
551
552
553
      /* take stream duration, fall back to avih duration */
      if ((duration = stream->duration) == -1)
        duration = avi->duration;

554
555
556
557
      gst_query_parse_duration (query, &fmt, NULL);

      switch (fmt) {
        case GST_FORMAT_TIME:
558
          gst_query_set_duration (query, fmt, duration);
559
560
          break;
        case GST_FORMAT_DEFAULT:
561
562
        {
          gint64 dur;
563
          GST_DEBUG_OBJECT (query, "total frames is %" G_GUINT32_FORMAT,
Wim Taymans's avatar
Wim Taymans committed
564
              stream->idx_n);
565

Wim Taymans's avatar
Wim Taymans committed
566
567
          if (stream->idx_n >= 0)
            gst_query_set_duration (query, fmt, stream->idx_n);
568
          else if (gst_pad_query_convert (pad, GST_FORMAT_TIME,
569
                  duration, &fmt, &dur))
570
            gst_query_set_duration (query, fmt, dur);
571
          break;
572
        }
573
574
575
576
        default:
          res = FALSE;
          break;
      }
Wim Taymans's avatar
Wim Taymans committed
577
      break;
578
    }
579
580
581
582
583
584
585
586
    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) {
587
          seekable = avi->seekable;
588
589
590
591
592
593
594
595
        }

        gst_query_set_seeking (query, GST_FORMAT_TIME, seekable,
            0, stream->duration);
        res = TRUE;
      }
      break;
    }
596
597
598
599
600
601
602
603
604
605
606
607
    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
        res = gst_pad_query_default (pad, query);
      break;
    }
Wim Taymans's avatar
Wim Taymans committed
608
    default:
609
      res = gst_pad_query_default (pad, query);
Wim Taymans's avatar
Wim Taymans committed
610
611
612
      break;
  }

613
  gst_object_unref (avi);
Wim Taymans's avatar
Wim Taymans committed
614
615
616
  return res;
}

617
#if 0
618
static const GstEventMask *
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
619
gst_avi_demux_get_event_mask (GstPad * pad)
620
621
{
  static const GstEventMask masks[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
622
623
    {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_KEY_UNIT},
    {0,}
624
625
626
627
  };

  return masks;
}
628
#endif
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
629

630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
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) {
647
        gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &val);
648
649
650
651
652
653
654
655
656
        GST_DEBUG_OBJECT (avi, "stream %d, previous entry at %"
            G_GUINT64_FORMAT, i, val);
        if (val < min)
          min = val;
      }
      continue;
    }

    if (!entry) {
657
      GST_DEBUG_OBJECT (avi, "no position for stream %d, assuming at start", i);
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
      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;
}

676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
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;
  guint index;

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

700
701
    /* compensate for chunk header */
    offset += 8;
702
703
704
705
706
    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);
707
    offset -= 8;
708
709
710
711
712
713

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

    if (before) {
      if (entry) {
714
        val = stream->index[index].offset;
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
        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;
    }

730
    val = stream->index[index].offset - 8;
731
732
733
    GST_DEBUG_OBJECT (avi, "stream %d, next entry at %" G_GUINT64_FORMAT, i,
        val);

734
    stream->current_total = stream->index[index].total;
735
736
737
738
739
740
    stream->current_entry = index;
  }

  return min;
}

741
742
#define GST_AVI_SEEK_PUSH_DISPLACE     (4 * GST_SECOND)

743
744
745
746
747
748
749
750
751
752
753
static gboolean
gst_avi_demux_handle_sink_event (GstPad * pad, GstEvent * event)
{
  gboolean res = TRUE;
  GstAviDemux *avi = GST_AVI_DEMUX (gst_pad_get_parent (pad));

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

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_NEWSEGMENT:
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
    {
      GstFormat format;
      gdouble rate, arate;
      gint64 start, stop, time, offset = 0;
      gboolean update;
      GstSegment segment;

      /* some debug output */
      gst_segment_init (&segment, GST_FORMAT_UNDEFINED);
      gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
          &start, &stop, &time);
      gst_segment_set_newsegment_full (&segment, update, rate, arate, format,
          start, stop, time);
      GST_DEBUG_OBJECT (avi,
          "received format %d newsegment %" GST_SEGMENT_FORMAT, format,
          &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 */
      if (format != GST_FORMAT_BYTES) {
        GST_DEBUG_OBJECT (avi, "unsupported segment format, ignoring");
        goto exit;
      }

783
784
      if (avi->have_index) {
        GstAviIndexEntry *entry;
785
        guint i = 0, index = 0, k = 0;
786
787
        GstAviStream *stream;

788
789
        /* compensate chunk header, stored index offset points after header */
        start += 8;
790
791
792
793
794
795
796
797
        /* 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,
798
              GST_SEARCH_MODE_AFTER, &start, NULL);
799

800
801
802
          if (entry == NULL)
            continue;
          index = entry - stream->index;
803

804
805
806
807
808
809
          /* 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 */
810
811
812
          if (stream->index[index].offset == start)
            break;
        } while (++i < avi->num_streams);
813
814
815
816
817
818
819
820
821
        start -= 8;
        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;
        }
822

823
        /* get the ts corresponding to start offset bytes for the stream */
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
        gst_avi_demux_get_buffer_info (avi, stream, index,
            (GstClockTime *) & time, NULL, NULL, NULL);
      } 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,
            GST_FORMAT_BYTES, start);

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

840
        gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &time);
841
        gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &offset);
842
843
      } else {
        GST_WARNING_OBJECT (avi, "no index data, forcing EOS");
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
        goto eos;
      }

      stop = GST_CLOCK_TIME_NONE;

      /* set up segment and send downstream */
      gst_segment_set_newsegment_full (&avi->segment, update, rate, arate,
          GST_FORMAT_TIME, time, stop, time);
      GST_DEBUG_OBJECT (avi, "Pushing newseg update %d, rate %g, "
          "applied rate %g, format %d, start %" G_GINT64_FORMAT ", "
          "stop %" G_GINT64_FORMAT, update, rate, arate, GST_FORMAT_TIME,
          time, stop);
      gst_avi_demux_push_event (avi,
          gst_event_new_new_segment_full (update, rate, arate, GST_FORMAT_TIME,
              time, stop, time));

      GST_DEBUG_OBJECT (avi, "next chunk expected at %" G_GINT64_FORMAT, start);

      /* adjust state for streaming thread accordingly */
863
864
865
866
      if (avi->have_index)
        gst_avi_demux_seek_streams_index (avi, offset, FALSE);
      else
        gst_avi_demux_seek_streams (avi, offset, FALSE);
867
868

      /* set up streaming thread */
869
870
871
      g_assert (offset >= start);
      avi->offset = start;
      avi->todrop = offset - start;
872
873

    exit:
874
      gst_event_unref (event);
875
      res = TRUE;
876
      break;
877
878
879
880
881
    eos:
      /* set up for EOS */
      avi->have_eos = TRUE;
      goto exit;
    }
882
883
884
885
886
887
888
889
890
891
892
893
    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;
    }
Wim Taymans's avatar
Wim Taymans committed
894
895
896
897
898
899
900
901
902
903
904
905
    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 */
    }
906
907
908
909
910
911
912
913
914
915
    default:
      res = gst_pad_event_default (pad, event);
      break;
  }

  gst_object_unref (avi);

  return res;
}

Wim Taymans's avatar
Wim Taymans committed
916
static gboolean
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
917
gst_avi_demux_handle_src_event (GstPad * pad, GstEvent * event)
Wim Taymans's avatar
Wim Taymans committed
918
919
{
  gboolean res = TRUE;
920
  GstAviDemux *avi = GST_AVI_DEMUX (gst_pad_get_parent (pad));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
921

922
  GST_DEBUG_OBJECT (avi,
923
      "have event type %s: %p on src pad", GST_EVENT_TYPE_NAME (event), event);
Wim Taymans's avatar
Wim Taymans committed
924
925
926

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
927
928
929
      if (!avi->streaming) {
        res = gst_avi_demux_handle_seek (avi, pad, event);
      } else {
930
        res = gst_avi_demux_handle_seek_push (avi, pad, event);
931
      }
932
      gst_event_unref (event);
933
934
935
936
937
      break;
    case GST_EVENT_QOS:
    case GST_EVENT_NAVIGATION:
      res = FALSE;
      gst_event_unref (event);
Wim Taymans's avatar
Wim Taymans committed
938
939
      break;
    default:
940
      res = gst_pad_event_default (pad, event);
Wim Taymans's avatar
Wim Taymans committed
941
942
      break;
  }
943
944
945

  gst_object_unref (avi);

Wim Taymans's avatar
Wim Taymans committed
946
947
948
  return res;
}

949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
/* 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
966
  if (gst_adapter_available (avi->adapter) < 8)
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
    return FALSE;

  data = gst_adapter_peek (avi->adapter, 8);
  *tag = GST_READ_UINT32_LE (data);
  *size = GST_READ_UINT32_LE (data + 4);

  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;