ed1113f4f5afb7e61dae436b2ca75bc6ceada63a
[platform/upstream/gstreamer.git] / validate / gst / validate / gst-validate-media-info.c
1 /* GStreamer
2  *
3  * Copyright (C) 2013 Collabora Ltd.
4  *  Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>
5  *
6  * gst-validate-media-info.c
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24
25 #ifdef HAVE_CONFIG_H
26 #  include "config.h"
27 #endif
28
29 #include "gst-validate-media-info.h"
30 #include "validate.h"
31
32 #include <glib/gstdio.h>
33 #include <string.h>
34
35 struct _GstValidateStreamInfo
36 {
37   GstCaps *caps;
38
39   GList *children;
40 };
41
42 static GstValidateStreamInfo *
43 gst_validate_stream_info_from_discoverer_info (GstDiscovererStreamInfo * info)
44 {
45   GstValidateStreamInfo *ret = g_new0 (GstValidateStreamInfo, 1);
46
47   ret->caps = gst_discoverer_stream_info_get_caps (info);
48   if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
49     GList *streams =
50         gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO
51         (info));
52     GList *iter;
53
54     for (iter = streams; iter; iter = g_list_next (iter)) {
55       ret->children = g_list_append (ret->children,
56           gst_validate_stream_info_from_discoverer_info (iter->data));
57     }
58     gst_discoverer_stream_info_list_free (streams);
59   }
60
61   return ret;
62 }
63
64 static GstValidateStreamInfo *
65 gst_validate_stream_info_from_caps_string (gchar * capsstr)
66 {
67   GstValidateStreamInfo *ret = g_new0 (GstValidateStreamInfo, 1);
68
69   ret->caps = gst_caps_from_string (capsstr);
70
71   return ret;
72 }
73
74 static void
75 gst_validate_stream_info_free (GstValidateStreamInfo * si)
76 {
77   if (si->caps)
78     gst_caps_unref (si->caps);
79   g_list_free_full (si->children,
80       (GDestroyNotify) gst_validate_stream_info_free);
81   g_free (si);
82 }
83
84 void
85 gst_validate_media_info_init (GstValidateMediaInfo * mi)
86 {
87   mi->uri = NULL;
88   mi->file_size = 0;
89   mi->duration = GST_CLOCK_TIME_NONE;
90   mi->seekable = FALSE;
91   mi->stream_info = NULL;
92   mi->playback_error = NULL;
93   mi->reverse_playback_error = NULL;
94   mi->track_switch_error = NULL;
95   mi->is_image = FALSE;
96   mi->discover_only = FALSE;
97 }
98
99 void
100 gst_validate_media_info_clear (GstValidateMediaInfo * mi)
101 {
102   g_free (mi->uri);
103   g_free (mi->playback_error);
104   g_free (mi->reverse_playback_error);
105   g_free (mi->track_switch_error);
106   if (mi->stream_info)
107     gst_validate_stream_info_free (mi->stream_info);
108 }
109
110 void
111 gst_validate_media_info_free (GstValidateMediaInfo * mi)
112 {
113   gst_validate_media_info_clear (mi);
114   g_free (mi);
115 }
116
117 gchar *
118 gst_validate_media_info_to_string (GstValidateMediaInfo * mi, gsize * length)
119 {
120   GKeyFile *kf = g_key_file_new ();
121   gchar *data = NULL;
122   gchar *str;
123
124   /* file info */
125   g_key_file_set_string (kf, "file-info", "uri", mi->uri);
126   g_key_file_set_uint64 (kf, "file-info", "file-size", mi->file_size);
127
128   /* media info */
129   g_key_file_set_uint64 (kf, "media-info", "file-duration", mi->duration);
130   g_key_file_set_boolean (kf, "media-info", "seekable", mi->seekable);
131   g_key_file_set_boolean (kf, "media-info", "is-image", mi->is_image);
132
133   if (mi->stream_info && mi->stream_info->caps) {
134     str = gst_caps_to_string (mi->stream_info->caps);
135     g_key_file_set_string (kf, "media-info", "caps", str);
136     g_free (str);
137   }
138
139   /* playback tests */
140   g_key_file_set_string (kf, "playback-tests", "playback-error",
141       mi->playback_error ? mi->playback_error : "");
142   g_key_file_set_string (kf, "playback-tests", "reverse-playback-error",
143       mi->reverse_playback_error ? mi->reverse_playback_error : "");
144   g_key_file_set_string (kf, "playback-tests", "track-switch-error",
145       mi->track_switch_error ? mi->track_switch_error : "");
146
147   data = g_key_file_to_data (kf, length, NULL);
148   g_key_file_free (kf);
149
150   return data;
151 }
152
153 gboolean
154 gst_validate_media_info_save (GstValidateMediaInfo * mi, const gchar * path,
155     GError ** err)
156 {
157   gchar *data = NULL;
158   gsize datalength = 0;
159
160   data = gst_validate_media_info_to_string (mi, &datalength);
161
162   if (!g_file_set_contents (path, data, datalength, err))
163     return FALSE;
164   return TRUE;
165 }
166
167 /**
168  * gst_validate_media_info_load: (skip):
169  */
170 GstValidateMediaInfo *
171 gst_validate_media_info_load (const gchar * path, GError ** err)
172 {
173   GKeyFile *kf = g_key_file_new ();
174   GstValidateMediaInfo *mi;
175   gchar *str;
176
177   if (!g_key_file_load_from_file (kf, path, G_KEY_FILE_NONE, err)) {
178     g_key_file_free (kf);
179     return NULL;
180   }
181
182   mi = g_new (GstValidateMediaInfo, 1);
183   gst_validate_media_info_init (mi);
184
185   mi->uri = g_key_file_get_string (kf, "file-info", "uri", err);
186   if (err && *err)
187     goto end;
188   mi->file_size = g_key_file_get_uint64 (kf, "file-info", "file-size", err);
189   if (err && *err)
190     goto end;
191
192   mi->duration =
193       g_key_file_get_uint64 (kf, "media-info", "file-duration", NULL);
194   mi->seekable = g_key_file_get_boolean (kf, "media-info", "seekable", NULL);
195   mi->is_image = g_key_file_get_boolean (kf, "media-info", "is-image", NULL);
196
197   str = g_key_file_get_string (kf, "media-info", "caps", NULL);
198   if (str) {
199     mi->stream_info = gst_validate_stream_info_from_caps_string (str);
200     g_free (str);
201   }
202
203   mi->playback_error =
204       g_key_file_get_string (kf, "playback-tests", "playback-error", NULL);
205   mi->reverse_playback_error =
206       g_key_file_get_string (kf, "playback-tests", "reverse-playback-error",
207       NULL);
208   mi->track_switch_error =
209       g_key_file_get_string (kf, "playback-tests", "track-switch-error", NULL);
210   if (mi->playback_error && strlen (mi->playback_error) == 0) {
211     g_free (mi->playback_error);
212     mi->playback_error = NULL;
213   }
214   if (mi->reverse_playback_error && strlen (mi->reverse_playback_error) == 0) {
215     g_free (mi->reverse_playback_error);
216     mi->reverse_playback_error = NULL;
217   }
218   if (mi->track_switch_error && strlen (mi->track_switch_error) == 0) {
219     g_free (mi->track_switch_error);
220     mi->track_switch_error = NULL;
221   }
222
223 end:
224   g_key_file_free (kf);
225   return mi;
226 }
227
228 static gboolean
229 check_file_size (GstValidateMediaInfo * mi)
230 {
231   GStatBuf statbuf;
232   gchar *filepath;
233   guint64 size = 0;
234   gboolean ret = TRUE;
235   GError *err = NULL;
236
237   filepath = g_filename_from_uri (mi->uri, NULL, &err);
238   if (!filepath) {
239     g_error_free (err);
240     return FALSE;
241   }
242
243   if (g_stat (filepath, &statbuf) == 0) {
244     size = statbuf.st_size;
245   } else {
246     ret = FALSE;
247     goto end;
248   }
249
250   mi->file_size = size;
251
252 end:
253   g_free (filepath);
254   return ret;
255 }
256
257 static gboolean
258 check_file_duration (GstValidateMediaInfo * mi, GstDiscovererInfo * info)
259 {
260   mi->duration = gst_discoverer_info_get_duration (info);
261   return TRUE;
262 }
263
264 static gboolean
265 check_seekable (GstValidateMediaInfo * mi, GstDiscovererInfo * info)
266 {
267   mi->seekable = gst_discoverer_info_get_seekable (info);
268   return TRUE;
269 }
270
271 #if 0
272 static inline gboolean
273 _gst_caps_can_intersect_safe (const GstCaps * a, const GstCaps * b)
274 {
275   if (a == b)
276     return TRUE;
277   if ((a == NULL) || (b == NULL))
278     return FALSE;
279   return gst_caps_can_intersect (a, b);
280 }
281
282 #if 0
283 typedef struct
284 {
285   GstEncodingProfile *profile;
286   gint count;
287 } ExpectedStream;
288
289 #define SET_MESSAGE(placeholder, msg) \
290 G_STMT_START {  \
291   if (placeholder) { \
292     *placeholder = msg; \
293   } \
294 } G_STMT_END
295
296 static gboolean
297 compare_encoding_profile_with_discoverer_stream (GstValidateFileChecker * fc,
298     GstEncodingProfile * prof, GstDiscovererStreamInfo * stream, gchar ** msg);
299
300 static gboolean
301     compare_container_profile_with_container_discoverer_stream
302     (GstValidateFileChecker * fc, GstEncodingContainerProfile * prof,
303     GstDiscovererContainerInfo * stream, gchar ** msg)
304 {
305   ExpectedStream *expected_streams = NULL;
306   GList *container_streams;
307   const GList *profile_iter;
308   const GList *streams_iter;
309   gint i;
310   gint expected_count = g_list_length ((GList *)
311       gst_encoding_container_profile_get_profiles (prof));
312   gboolean ret = TRUE;
313
314   container_streams = gst_discoverer_container_info_get_streams (stream);
315
316   if (expected_count == 0) {
317     if (g_list_length (container_streams) != 0) {
318       SET_MESSAGE (msg,
319           g_strdup_printf
320           ("No streams expected on this container, but found %u",
321               g_list_length (container_streams)));
322       ret = FALSE;
323       goto end;
324     }
325   }
326
327   /* initialize expected streams data */
328   expected_streams = g_malloc0 (sizeof (ExpectedStream) * expected_count);
329   for (i = 0, profile_iter = gst_encoding_container_profile_get_profiles (prof);
330       profile_iter; profile_iter = g_list_next (profile_iter), i++) {
331     GstEncodingProfile *prof = profile_iter->data;
332     ExpectedStream *expected = &(expected_streams[i]);
333
334     expected->profile = prof;
335   }
336
337   /* look for the streams on discoverer info */
338   for (streams_iter = container_streams; streams_iter;
339       streams_iter = g_list_next (streams_iter)) {
340     GstDiscovererStreamInfo *info = streams_iter->data;
341     gboolean found = FALSE;
342     for (i = 0; i < expected_count; i++) {
343       ExpectedStream *expected = &(expected_streams[i]);
344
345       if (compare_encoding_profile_with_discoverer_stream (fc,
346               expected->profile, info, NULL)) {
347         found = TRUE;
348         break;
349       }
350     }
351
352     if (!found) {
353       GstCaps *caps = gst_discoverer_stream_info_get_caps (info);
354       gchar *caps_str = gst_caps_to_string (caps);
355       SET_MESSAGE (msg,
356           g_strdup_printf ("Stream with caps '%s' wasn't found on file",
357               caps_str));
358       g_free (caps_str);
359       gst_caps_unref (caps);
360       ret = FALSE;
361       goto end;
362     }
363   }
364
365   /* check if all expected streams are present */
366   for (i = 0; i < expected_count; i++) {
367     ExpectedStream *expected = &(expected_streams[i]);
368     guint presence = gst_encoding_profile_get_presence (expected->profile);
369
370     if (presence == 0)
371       continue;
372
373     if (presence != expected->count) {
374       gchar *caps_str =
375           gst_caps_to_string (gst_encoding_profile_get_format
376           (expected->profile));
377       SET_MESSAGE (msg,
378           g_strdup_printf ("Stream from profile %s (with caps '%s"
379               "' has presence %u but the number of streams found was %d",
380               gst_encoding_profile_get_name (expected->profile), caps_str,
381               presence, expected->count));
382       g_free (caps_str);
383       ret = FALSE;
384       goto end;
385     }
386   }
387
388 end:
389   g_free (expected_streams);
390   gst_discoverer_stream_info_list_free (container_streams);
391   return ret;
392 }
393
394 static gboolean
395 compare_encoding_profile_with_discoverer_stream (GstValidateFileChecker * fc,
396     GstEncodingProfile * prof, GstDiscovererStreamInfo * stream, gchar ** msg)
397 {
398   gboolean ret = TRUE;
399   GstCaps *caps = NULL;
400   const GstCaps *profile_caps;
401   const GstCaps *restriction_caps;
402
403   caps = gst_discoverer_stream_info_get_caps (stream);
404   profile_caps = gst_encoding_profile_get_format (prof);
405   restriction_caps = gst_encoding_profile_get_restriction (prof);
406
407   /* TODO need to consider profile caps restrictions */
408   if (!_gst_caps_can_intersect_safe (caps, profile_caps)) {
409     gchar *caps_str = gst_caps_to_string (caps);
410     gchar *profile_caps_str = gst_caps_to_string (profile_caps);
411     SET_MESSAGE (msg, g_strdup_printf ("Caps '%s' didn't match profile '%s'",
412             profile_caps_str, caps_str));
413     g_free (caps_str);
414     g_free (profile_caps_str);
415     ret = FALSE;
416     goto end;
417   }
418
419   if (restriction_caps) {
420     GstStructure *structure;
421     gint i;
422     gboolean found = FALSE;
423
424     for (i = 0; i < gst_caps_get_size (restriction_caps); i++) {
425       structure = gst_caps_get_structure (restriction_caps, i);
426       structure = gst_structure_copy (structure);
427       gst_structure_set_name (structure,
428           gst_structure_get_name (gst_caps_get_structure (caps, 0)));
429       if (gst_structure_can_intersect (structure, gst_caps_get_structure (caps,
430                   0))) {
431         gst_structure_free (structure);
432         found = TRUE;
433         break;
434       }
435       gst_structure_free (structure);
436     }
437     if (!found) {
438       gchar *caps_str = gst_caps_to_string (caps);
439       gchar *restriction_caps_str = gst_caps_to_string (restriction_caps);
440       SET_MESSAGE (msg,
441           g_strdup_printf ("Caps restriction '%s' wasn't respected on file "
442               "with caps '%s'", restriction_caps_str, caps_str));
443       g_free (caps_str);
444       g_free (restriction_caps_str);
445       ret = FALSE;
446       goto end;
447     }
448   }
449
450   if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) {
451     if (GST_IS_DISCOVERER_CONTAINER_INFO (stream)) {
452       ret =
453           ret & compare_container_profile_with_container_discoverer_stream (fc,
454           (GstEncodingContainerProfile *) prof,
455           (GstDiscovererContainerInfo *) stream, msg);
456     } else {
457       SET_MESSAGE (msg,
458           g_strdup_printf ("Expected container profile but found stream of %s",
459               gst_discoverer_stream_info_get_stream_type_nick (stream)));
460       ret = FALSE;
461       goto end;
462     }
463
464   } else if (GST_IS_ENCODING_VIDEO_PROFILE (prof)) {
465     if (!GST_IS_DISCOVERER_VIDEO_INFO (stream)) {
466       SET_MESSAGE (msg,
467           g_strdup_printf ("Expected video profile but found stream of %s",
468               gst_discoverer_stream_info_get_stream_type_nick (stream)));
469       ret = FALSE;
470       goto end;
471     }
472
473   } else if (GST_IS_ENCODING_AUDIO_PROFILE (prof)) {
474     if (!GST_IS_DISCOVERER_AUDIO_INFO (stream)) {
475       SET_MESSAGE (msg,
476           g_strdup_printf ("Expected audio profile but found stream of %s",
477               gst_discoverer_stream_info_get_stream_type_nick (stream)));
478       ret = FALSE;
479       goto end;
480     }
481   } else {
482     g_assert_not_reached ();
483     return FALSE;
484   }
485
486
487 end:
488   if (caps)
489     gst_caps_unref (caps);
490
491   return ret;
492 }
493 #endif
494 #endif
495
496 static gboolean
497 check_encoding_profile (GstValidateMediaInfo * mi, GstDiscovererInfo * info)
498 {
499   gboolean ret = TRUE;
500   GstDiscovererStreamInfo *streaminfo;
501
502   streaminfo = gst_discoverer_info_get_stream_info (info);
503   mi->stream_info = gst_validate_stream_info_from_discoverer_info (streaminfo);
504
505   gst_discoverer_info_unref (streaminfo);
506
507   return ret;
508 }
509
510 typedef gboolean (*GstElementConfigureFunc) (GstValidateMediaInfo *,
511     GstElement *, gchar ** msg);
512 static gboolean
513 check_playback_scenario (GstValidateMediaInfo * mi,
514     GstElementConfigureFunc configure_function, gchar ** error_message)
515 {
516   GstElement *playbin;
517   GstElement *videosink, *audiosink;
518   GstBus *bus;
519   GstMessage *msg;
520   gboolean ret = TRUE;
521   GstStateChangeReturn state_ret;
522
523   playbin = gst_element_factory_make ("playbin", "fc-playbin");
524   videosink = gst_element_factory_make ("fakesink", "fc-videosink");
525   audiosink = gst_element_factory_make ("fakesink", "fc-audiosink");
526
527   if (!playbin || !videosink || !audiosink) {
528     *error_message = g_strdup ("Playbin and/or fakesink not available");
529   }
530
531   g_object_set (playbin, "video-sink", videosink, "audio-sink", audiosink,
532       "uri", mi->uri, NULL);
533
534   bus = gst_pipeline_get_bus (GST_PIPELINE (playbin));
535
536   state_ret = gst_element_set_state (playbin, GST_STATE_PAUSED);
537   if (state_ret == GST_STATE_CHANGE_FAILURE) {
538     *error_message = g_strdup ("Failed to change pipeline to paused");
539     ret = FALSE;
540     goto end;
541   } else if (state_ret == GST_STATE_CHANGE_ASYNC) {
542     msg =
543         gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
544         GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_EOS | GST_MESSAGE_ERROR);
545     if (msg && GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ASYNC_DONE) {
546       gst_message_unref (msg);
547     } else {
548       ret = FALSE;
549       *error_message = g_strdup ("Playback finihshed unexpectedly");
550       goto end;
551     }
552   }
553
554   if (configure_function) {
555     if (!configure_function (mi, playbin, error_message)) {
556       gst_object_unref (bus);
557       gst_object_unref (playbin);
558       return FALSE;
559     }
560   }
561
562   if (gst_element_set_state (playbin,
563           GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
564     *error_message = g_strdup ("Failed to set pipeline to playing");
565     ret = FALSE;
566     goto end;
567   }
568
569   msg =
570       gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
571       GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
572   if (msg) {
573     if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) {
574       /* all good */
575       ret = TRUE;
576     } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
577       GError *error = NULL;
578       gchar *debug = NULL;
579
580       gst_message_parse_error (msg, &error, &debug);
581       *error_message = g_strdup_printf ("Playback error: %s : %s",
582           error->message, debug);
583       g_error_free (error);
584       g_free (debug);
585
586       ret = FALSE;
587     } else {
588       g_assert_not_reached ();
589     }
590     gst_message_unref (msg);
591   } else {
592     ret = FALSE;
593     *error_message = g_strdup ("Playback finihshed unexpectedly");
594   }
595
596 end:
597   gst_object_unref (bus);
598   gst_element_set_state (playbin, GST_STATE_NULL);
599   gst_object_unref (playbin);
600
601   return ret;
602 }
603
604 static gboolean
605 check_playback (GstValidateMediaInfo * mi, gchar ** msg)
606 {
607   return check_playback_scenario (mi, NULL, msg);
608 }
609
610 static gboolean
611 send_reverse_seek (GstValidateMediaInfo * mi, GstElement * pipeline,
612     gchar ** msg)
613 {
614   gboolean ret;
615
616   ret = gst_element_seek (pipeline, -1.0, GST_FORMAT_TIME,
617       GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, -1);
618
619   if (!ret) {
620     *msg = g_strdup ("Reverse playback seek failed");
621   }
622   return ret;
623 }
624
625 static gboolean
626 check_reverse_playback (GstValidateMediaInfo * mi, gchar ** msg)
627 {
628   return check_playback_scenario (mi, send_reverse_seek, msg);
629 }
630
631 typedef struct
632 {
633   guint counter;
634   guint back_counter;
635   gulong probe_id;
636   GstPad *pad;
637 } BufferCountData;
638
639 static GstPadProbeReturn
640 input_selector_pad_probe (GstPad * pad, GstPadProbeInfo * info,
641     gpointer userdata)
642 {
643   GstPad *sink_pad = NULL;
644
645   if (info->type == GST_PAD_PROBE_TYPE_BUFFER) {
646     BufferCountData *bcd =
647         g_object_get_data (G_OBJECT (pad), "buffer-count-data");
648     if (!bcd) {
649       GST_ERROR_OBJECT (pad, "No buffer-count-data found");
650       return GST_PAD_PROBE_OK;
651     }
652
653     ++bcd->counter;
654     if (GST_PAD_IS_SRC (pad)) {
655       g_object_get (GST_PAD_PARENT (pad), "active-pad", &sink_pad, NULL);
656       if (sink_pad) {
657         bcd = g_object_get_data (G_OBJECT (sink_pad), "buffer-count-data");
658         if (!bcd) {
659           gst_object_unref (sink_pad);
660           GST_ERROR_OBJECT (pad, "No buffer-count-data found");
661           return GST_PAD_PROBE_OK;
662         }
663         ++bcd->back_counter;
664         gst_object_unref (sink_pad);
665       }
666     }
667   }
668   return GST_PAD_PROBE_OK;
669 }
670
671 static void
672 setup_input_selector_counters (GstElement * element)
673 {
674   GstIterator *iterator;
675   gboolean done = FALSE;
676   GValue value = { 0, };
677   GstPad *pad;
678   BufferCountData *bcd;
679
680   iterator = gst_element_iterate_pads (element);
681   while (!done) {
682     switch (gst_iterator_next (iterator, &value)) {
683       case GST_ITERATOR_OK:
684         pad = g_value_dup_object (&value);
685         bcd = g_slice_new0 (BufferCountData);
686         g_object_set_data (G_OBJECT (pad), "buffer-count-data", bcd);
687         bcd->probe_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
688             (GstPadProbeCallback) input_selector_pad_probe, NULL, NULL);
689         bcd->pad = pad;
690         g_value_reset (&value);
691         break;
692       case GST_ITERATOR_RESYNC:
693         gst_iterator_resync (iterator);
694         break;
695       case GST_ITERATOR_ERROR:
696         done = TRUE;
697         break;
698       case GST_ITERATOR_DONE:
699         done = TRUE;
700         break;
701     }
702   }
703   gst_iterator_free (iterator);
704 }
705
706 static gboolean
707 check_and_remove_input_selector_counters (GstElement * element,
708     gchar ** error_message)
709 {
710   GstIterator *iterator;
711   gboolean done = FALSE;
712   GstPad *pad;
713   GValue value = { 0, };
714   guint id, ncounters = 0, total_sink_count = 0;
715   BufferCountData *bcd, **bcds =
716       g_malloc0 (sizeof (BufferCountData *) * element->numpads);
717   gboolean ret = TRUE;
718
719   /* First gather all counts, and free memory, etc */
720   iterator = gst_element_iterate_pads (element);
721   while (!done) {
722     switch (gst_iterator_next (iterator, &value)) {
723       case GST_ITERATOR_OK:
724         pad = g_value_get_object (&value);
725         bcd = g_object_get_data (G_OBJECT (pad), "buffer-count-data");
726         if (GST_PAD_IS_SINK (pad)) {
727           bcds[++ncounters] = bcd;
728           total_sink_count += bcd->counter;
729         } else {
730           bcds[0] = bcd;
731         }
732         gst_pad_remove_probe (pad, bcd->probe_id);
733         g_value_reset (&value);
734         break;
735       case GST_ITERATOR_RESYNC:
736         gst_iterator_resync (iterator);
737         break;
738       case GST_ITERATOR_ERROR:
739         done = TRUE;
740         *error_message = g_strdup ("Failed to iterate through pads");
741         ret = FALSE;
742         break;
743       case GST_ITERATOR_DONE:
744         done = TRUE;
745         break;
746     }
747   }
748   gst_iterator_free (iterator);
749
750   if (!ret) {
751     g_free (bcds);
752     return FALSE;
753   }
754
755   /* Now bcd[0] contains the total number of buffers received,
756      and subsequent bcd slots contain the total number of buffers sent
757      by each source pad. Check that the totals match, and that every
758      source pad got at least one buffer.
759      Or that's the theory. It doesn't work in practice, the number of
760      raw buffers flowing is non deterministic. */
761 #if 0
762   if (bcds[0]->counter != total_sink_count) {
763     *error_message = g_strdup_printf ("%u buffers received, %u buffers sent",
764         total_sink_count, bcds[0]->counter);
765     ret = FALSE;
766   }
767   for (id = 1; id < element->numpads; ++id) {
768     if (bcds[id]->counter == 0) {
769       *error_message =
770           g_strdup_printf ("Sink pad %s got no buffers",
771           GST_PAD_NAME (bcds[id]->pad));
772       ret = FALSE;
773     }
774   }
775 #endif
776   /* We at least check that at least one buffer was sent while the
777      selected sink was a given sink, for all sinks */
778   for (id = 1; id < element->numpads; ++id) {
779     if (bcds[id]->back_counter == 0) {
780       *error_message =
781           g_strdup_printf ("No buffer was sent while sink pad %s was active",
782           GST_PAD_NAME (bcds[id]->pad));
783       ret = FALSE;
784     }
785   }
786
787   for (id = 0; id < element->numpads; ++id) {
788     gst_object_unref (bcds[id]->pad);
789     g_slice_free (BufferCountData, bcds[id]);
790   }
791   g_free (bcds);
792   return ret;
793 }
794
795 static GstPad *
796 find_next_pad (GstElement * element, GstPad * pad)
797 {
798   GstIterator *iterator;
799   gboolean done = FALSE, pick = FALSE;
800   GstPad *tmp, *next = NULL, *first = NULL;
801   GValue value = { 0, };
802
803   iterator = gst_element_iterate_sink_pads (element);
804
805   while (!done) {
806     switch (gst_iterator_next (iterator, &value)) {
807       case GST_ITERATOR_OK:
808         tmp = g_value_dup_object (&value);
809         if (first == NULL)
810           first = gst_object_ref (tmp);
811         if (pick) {
812           next = tmp;
813           done = TRUE;
814         } else {
815           pick = (tmp == pad);
816           gst_object_unref (tmp);
817         }
818         g_value_reset (&value);
819         break;
820       case GST_ITERATOR_RESYNC:
821         gst_iterator_resync (iterator);
822         break;
823       case GST_ITERATOR_ERROR:
824         done = TRUE;
825         break;
826       case GST_ITERATOR_DONE:
827         /* When we reach the end, we may be in the case where the pad
828            to search from was the last one in the list, in which case
829            we want to return the first pad. */
830         if (pick) {
831           next = first;
832           first = NULL;
833         }
834         done = TRUE;
835         break;
836     }
837   }
838   gst_iterator_free (iterator);
839   if (first)
840     gst_object_unref (first);
841   return next;
842 }
843
844 static int
845 find_input_selector (GValue * value, void *userdata)
846 {
847   GstElement *element = g_value_get_object (value);
848   g_assert (GST_IS_ELEMENT (element));
849   if (g_str_has_prefix (GST_ELEMENT_NAME (element), "inputselector")) {
850     guint npads;
851     g_object_get (element, "n-pads", &npads, NULL);
852     if (npads > 1)
853       return 0;
854   }
855   return !0;
856 }
857
858 /* This function looks for an input-selector, and, if one is found,
859    cycle through its sink pads */
860 static gboolean
861 check_track_selection (GstValidateMediaInfo * mi, gchar ** error_message)
862 {
863   GstElement *playbin;
864   GstElement *videosink, *audiosink;
865   GstElement *input_selector = NULL;
866   GstBus *bus;
867   GstMessage *msg;
868   gboolean ret = TRUE;
869   GstStateChangeReturn state_ret;
870   GstIterator *iterator;
871   GstPad *original_pad;
872   static const GstClockTime switch_delay = GST_SECOND * 5;
873   GValue value = { 0, };
874
875   playbin = gst_element_factory_make ("playbin", "fc-playbin");
876   videosink = gst_element_factory_make ("fakesink", "fc-videosink");
877   audiosink = gst_element_factory_make ("fakesink", "fc-audiosink");
878
879   if (!playbin || !videosink || !audiosink) {
880     *error_message = g_strdup ("Playbin and/or fakesink not available");
881   }
882
883   g_object_set (playbin, "video-sink", videosink, "audio-sink", audiosink,
884       "uri", mi->uri, NULL);
885   g_object_set (videosink, "sync", TRUE, NULL);
886   g_object_set (audiosink, "sync", TRUE, NULL);
887
888   bus = gst_pipeline_get_bus (GST_PIPELINE (playbin));
889
890   state_ret = gst_element_set_state (playbin, GST_STATE_PAUSED);
891   if (state_ret == GST_STATE_CHANGE_FAILURE) {
892     *error_message = g_strdup ("Failed to change pipeline to paused");
893     ret = FALSE;
894     goto end;
895   } else if (state_ret == GST_STATE_CHANGE_ASYNC) {
896     msg =
897         gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
898         GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_EOS | GST_MESSAGE_ERROR);
899     if (msg && GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ASYNC_DONE) {
900       gst_message_unref (msg);
901     } else {
902       ret = FALSE;
903       *error_message = g_strdup ("Playback finihshed unexpectedly");
904       goto end;
905     }
906   }
907
908   if (gst_element_set_state (playbin,
909           GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
910     *error_message = g_strdup ("Failed to set pipeline to playing");
911     ret = FALSE;
912     goto end;
913   }
914
915   iterator = gst_bin_iterate_recurse (GST_BIN (playbin));
916   if (!gst_iterator_find_custom (iterator,
917           (GCompareFunc) find_input_selector, &value, NULL)) {
918     /* It's fine, there's only one if several tracks of the same type */
919     gst_iterator_free (iterator);
920     input_selector = NULL;
921     goto end;
922   }
923   input_selector = g_value_dup_object (&value);
924   g_value_reset (&value);
925   gst_iterator_free (iterator);
926   g_object_get (input_selector, "active-pad", &original_pad, NULL);
927   if (!original_pad) {
928     /* Unexpected, log an error somehow ? */
929     ret = FALSE;
930     gst_object_unref (input_selector);
931     input_selector = NULL;
932     goto end;
933   }
934
935   /* Attach a buffer counter to each pad */
936   setup_input_selector_counters (input_selector);
937
938   while (1) {
939     msg =
940         gst_bus_timed_pop_filtered (bus, switch_delay,
941         GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
942     if (msg) {
943       if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) {
944         /* all good */
945         ret = TRUE;
946       } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
947         GError *error = NULL;
948         gchar *debug = NULL;
949
950         gst_message_parse_error (msg, &error, &debug);
951         *error_message = g_strdup_printf ("Playback error: %s : %s",
952             error->message, debug);
953         g_error_free (error);
954         g_free (debug);
955
956         ret = FALSE;
957       } else {
958         g_assert_not_reached ();
959       }
960       gst_message_unref (msg);
961     } else {
962       /* Timeout, switch track if we have more, or stop */
963       GstPad *active_pad, *next_pad;
964
965       g_object_get (input_selector, "active-pad", &active_pad, NULL);
966       if (!active_pad) {
967         *error_message =
968             g_strdup ("Failed to get active-pad from input-selector");
969         ret = FALSE;
970         goto end;
971       }
972       next_pad = find_next_pad (input_selector, active_pad);
973       gst_object_unref (active_pad);
974       if (!next_pad) {
975         ret = FALSE;
976         goto end;
977       }
978       if (next_pad == original_pad) {
979         goto end;
980       }
981       g_object_set (input_selector, "active-pad", next_pad, NULL);
982       gst_object_unref (next_pad);
983     }
984   }
985
986 end:
987   if (input_selector) {
988     if (!check_and_remove_input_selector_counters (input_selector,
989             error_message))
990       ret = FALSE;
991     gst_object_unref (input_selector);
992   }
993   gst_object_unref (bus);
994   gst_element_set_state (playbin, GST_STATE_NULL);
995   gst_object_unref (playbin);
996
997   return ret;
998 }
999
1000 static gboolean
1001 check_is_image (GstDiscovererInfo * info)
1002 {
1003   gboolean ret = FALSE;
1004   GList *video_streams = gst_discoverer_info_get_video_streams (info);
1005
1006   if (g_list_length (video_streams) == 1) {
1007     if (gst_discoverer_video_info_is_image (video_streams->data)) {
1008       GList *audio_streams = gst_discoverer_info_get_audio_streams (info);
1009
1010       if (audio_streams == NULL)
1011         ret = TRUE;
1012       else
1013         gst_discoverer_stream_info_list_free (audio_streams);
1014     }
1015   }
1016
1017   gst_discoverer_stream_info_list_free (video_streams);
1018
1019   return ret;
1020 }
1021
1022 gboolean
1023 gst_validate_media_info_inspect_uri (GstValidateMediaInfo * mi,
1024     const gchar * uri, gboolean discover_only, GError ** err)
1025 {
1026   GstDiscovererInfo *info;
1027   GstDiscoverer *discoverer = gst_discoverer_new (GST_SECOND * 60, err);
1028   gboolean ret = TRUE;
1029
1030   g_return_val_if_fail (uri != NULL, FALSE);
1031
1032   g_free (mi->uri);
1033   mi->uri = g_strdup (uri);
1034
1035   if (!discoverer) {
1036     return FALSE;
1037   }
1038
1039   info = gst_discoverer_discover_uri (discoverer, uri, err);
1040
1041   if (gst_discoverer_info_get_result (info) != GST_DISCOVERER_OK) {
1042     gst_object_unref (discoverer);
1043     return FALSE;
1044   }
1045
1046   mi->is_image = check_is_image (info);
1047   ret = check_file_size (mi) & ret;
1048   ret = check_encoding_profile (mi, info) & ret;
1049   ret = check_file_duration (mi, info) & ret;
1050
1051   if (mi->is_image)
1052     goto done;
1053
1054   check_seekable (mi, info);
1055   if (discover_only)
1056     goto done;
1057
1058   ret = check_playback (mi, &mi->playback_error) & ret;
1059   ret = check_reverse_playback (mi, &mi->reverse_playback_error) & ret;
1060   ret = check_track_selection (mi, &mi->track_switch_error) & ret;
1061
1062 done:
1063   gst_object_unref (discoverer);
1064
1065   return ret;
1066 }
1067
1068 gboolean
1069 gst_validate_media_info_compare (GstValidateMediaInfo * expected,
1070     GstValidateMediaInfo * extracted)
1071 {
1072   gboolean ret = TRUE;
1073   if (expected->duration != extracted->duration) {
1074     gst_validate_printf (NULL,
1075         "Duration changed: %" GST_TIME_FORMAT " -> %" GST_TIME_FORMAT "\n",
1076         GST_TIME_ARGS (expected->duration),
1077         GST_TIME_ARGS (extracted->duration));
1078     ret = FALSE;
1079   }
1080   if (expected->file_size != extracted->file_size) {
1081     gst_validate_printf (NULL,
1082         "File size changed: %" G_GUINT64_FORMAT " -> %" G_GUINT64_FORMAT "\n",
1083         expected->file_size, extracted->file_size);
1084     ret = FALSE;
1085   }
1086   if (expected->seekable && !extracted->seekable) {
1087     gst_validate_printf (NULL, "File isn't seekable anymore\n");
1088     ret = FALSE;
1089   }
1090
1091   if (extracted->discover_only == FALSE) {
1092     if (expected->playback_error == NULL && extracted->playback_error) {
1093       gst_validate_printf (NULL, "Playback is now failing with: %s\n",
1094           extracted->playback_error);
1095       ret = FALSE;
1096     }
1097     if (expected->reverse_playback_error == NULL
1098         && extracted->reverse_playback_error) {
1099       gst_validate_printf (NULL, "Reverse playback is now failing with: %s\n",
1100           extracted->reverse_playback_error);
1101       ret = FALSE;
1102     }
1103     if (expected->track_switch_error == NULL && extracted->track_switch_error) {
1104       gst_validate_printf (NULL, "Track switching is now failing with: %s\n",
1105           extracted->track_switch_error);
1106       ret = FALSE;
1107     }
1108   }
1109
1110   if (extracted->stream_info == NULL || expected->stream_info == NULL) {
1111     gst_validate_printf (NULL,
1112         "Stream infos could not be retrieved, an error occured\n");
1113     ret = FALSE;
1114   } else if (expected->stream_info
1115       && !gst_caps_is_equal_fixed (expected->stream_info->caps,
1116           extracted->stream_info->caps)) {
1117     gchar *caps1 = gst_caps_to_string (expected->stream_info->caps);
1118     gchar *caps2 = gst_caps_to_string (extracted->stream_info->caps);
1119
1120     gst_validate_printf (NULL, "Media caps changed: '%s' -> '%s'\n", caps1,
1121         caps2);
1122     g_free (caps1);
1123     g_free (caps2);
1124     ret = FALSE;
1125   }
1126   return ret;
1127 }