packaging: add packaging folder and update submodule pkgs
[platform/upstream/gstreamer-vaapi.git] / tests / simple-decoder.c
1 /*
2  *  simple-decoder.c - Simple Decoder Application
3  *
4  *  Copyright (C) 2013-2014 Intel Corporation
5  *    Author: Gwenole Beauchesne <gwenole.beauchesne@intel.com>
6  *
7  *  This library is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU Lesser General Public License
9  *  as published by the Free Software Foundation; either version 2.1
10  *  of the License, or (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this library; if not, write to the Free
19  *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  *  Boston, MA 02110-1301 USA
21  */
22
23 /*
24  * This is a really simple decoder application that only accepts raw
25  * bitstreams. So, it may be needed to suggest what codec to use to
26  * the application.
27  */
28
29 #include "gst/vaapi/sysdeps.h"
30 #include <stdarg.h>
31 #include <gst/vaapi/gstvaapidecoder.h>
32 #include <gst/vaapi/gstvaapidecoder_h264.h>
33 #include <gst/vaapi/gstvaapidecoder_jpeg.h>
34 #include <gst/vaapi/gstvaapidecoder_mpeg2.h>
35 #include <gst/vaapi/gstvaapidecoder_mpeg4.h>
36 #include <gst/vaapi/gstvaapidecoder_vc1.h>
37 #include <gst/vaapi/gstvaapiwindow.h>
38 #include <gst/vaapi/gstvaapipixmap.h>
39 #include "codec.h"
40 #include "output.h"
41
42 static gchar *g_codec_str;
43 static gboolean g_use_pixmap;
44 static gboolean g_benchmark;
45
46 static GOptionEntry g_options[] = {
47     { "codec", 'c',
48       0,
49       G_OPTION_ARG_STRING, &g_codec_str,
50       "suggested codec", NULL },
51     { "pixmap", 0,
52       0,
53       G_OPTION_ARG_NONE, &g_use_pixmap,
54       "use render-to-pixmap", NULL },
55     { "benchmark", 0,
56       0,
57       G_OPTION_ARG_NONE, &g_benchmark,
58       "benchmark mode", NULL },
59     { NULL, }
60 };
61
62 typedef enum {
63     APP_RUNNING,
64     APP_GOT_EOS,
65     APP_GOT_ERROR,
66 } AppEvent;
67
68 typedef enum {
69     APP_ERROR_NONE,
70     APP_ERROR_DECODER,
71     APP_ERROR_RENDERER,
72 } AppError;
73
74 typedef struct {
75     GstVaapiSurfaceProxy *proxy;
76     GstClockTime        pts;
77     GstClockTime        duration;
78 } RenderFrame;
79
80 typedef struct {
81     GMutex              mutex;
82     GMappedFile        *file;
83     gchar              *file_name;
84     guint               file_offset;
85     guint               file_size;
86     guchar             *file_data;
87     GstVaapiDisplay    *display;
88     GstVaapiDecoder    *decoder;
89     GThread            *decoder_thread;
90     volatile gboolean   decoder_thread_cancel;
91     GCond               decoder_ready;
92     GAsyncQueue        *decoder_queue;
93     GstVaapiCodec       codec;
94     guint               fps_n;
95     guint               fps_d;
96     guint32             frame_duration;
97     guint               surface_width;
98     guint               surface_height;
99     GstVaapiPixmap     *pixmaps[2];
100     guint               pixmap_id;
101     guint               pixmap_width;
102     guint               pixmap_height;
103     GstVaapiWindow     *window;
104     guint               window_width;
105     guint               window_height;
106     GThread            *render_thread;
107     volatile gboolean   render_thread_cancel;
108     GCond               render_ready;
109     RenderFrame        *last_frame;
110     GError             *error;
111     AppEvent            event;
112     GCond               event_cond;
113     GTimer             *timer;
114     guint32             num_frames;
115 } App;
116
117 static inline RenderFrame *
118 render_frame_new(void)
119 {
120     return g_slice_new(RenderFrame);
121 }
122
123 static void
124 render_frame_free(RenderFrame *rfp)
125 {
126     if (G_UNLIKELY(!rfp))
127         return;
128     gst_vaapi_surface_proxy_replace(&rfp->proxy, NULL);
129     g_slice_free(RenderFrame, rfp);
130 }
131
132 static inline void
133 render_frame_replace(RenderFrame **rfp_ptr, RenderFrame *new_rfp)
134 {
135     if (*rfp_ptr)
136         render_frame_free(*rfp_ptr);
137     *rfp_ptr = new_rfp;
138 }
139
140 #define APP_ERROR app_error_quark()
141 static GQuark
142 app_error_quark(void)
143 {
144     static gsize g_quark;
145
146     if (g_once_init_enter(&g_quark)) {
147         gsize quark = (gsize)g_quark_from_static_string("AppError");
148         g_once_init_leave(&g_quark, quark);
149     }
150     return g_quark;
151 }
152
153 static void
154 app_send_error(App *app, GError *error)
155 {
156     g_mutex_lock(&app->mutex);
157     app->error = error;
158     app->event = APP_GOT_ERROR;
159     g_cond_signal(&app->event_cond);
160     g_mutex_unlock(&app->mutex);
161 }
162
163 static void
164 app_send_eos(App *app)
165 {
166     g_mutex_lock(&app->mutex);
167     app->event = APP_GOT_EOS;
168     g_cond_signal(&app->event_cond);
169     g_mutex_unlock(&app->mutex);
170 }
171
172 static const gchar *
173 get_decoder_status_string(GstVaapiDecoderStatus status)
174 {
175     const gchar *str;
176
177 #define DEFINE_STATUS(status, status_string) \
178     case GST_VAAPI_DECODER_STATUS_##status:  \
179         str = status_string;                 \
180         break
181
182     switch (status) {
183         DEFINE_STATUS(SUCCESS,                  "<success>");
184         DEFINE_STATUS(END_OF_STREAM,            "<EOS>");
185         DEFINE_STATUS(ERROR_ALLOCATION_FAILED,  "allocation failed");
186         DEFINE_STATUS(ERROR_INIT_FAILED,        "initialization failed");
187         DEFINE_STATUS(ERROR_UNSUPPORTED_CODEC,  "unsupported codec");
188         DEFINE_STATUS(ERROR_NO_DATA,            "not enough data");
189         DEFINE_STATUS(ERROR_NO_SURFACE,         "no surface vailable");
190         DEFINE_STATUS(ERROR_INVALID_SURFACE,    "invalid surface");
191         DEFINE_STATUS(ERROR_BITSTREAM_PARSER,   "bitstream parser error");
192         DEFINE_STATUS(ERROR_UNSUPPORTED_PROFILE,
193                       "unsupported profile");
194         DEFINE_STATUS(ERROR_UNSUPPORTED_CHROMA_FORMAT,
195                       "unsupported chroma-format");
196         DEFINE_STATUS(ERROR_INVALID_PARAMETER,  "invalid parameter");
197     default:
198         str = "<unknown>";
199         break;
200     }
201 #undef DEFINE_STATUS
202
203     return str;
204 }
205
206 static const gchar *
207 get_error_string(AppError error)
208 {
209     const gchar *str;
210
211 #define DEFINE_ERROR(error, error_string)       \
212     case APP_ERROR_##error:                     \
213         str = error_string;                     \
214         break
215
216     switch (error) {
217         DEFINE_ERROR(NONE,      "<none>");
218         DEFINE_ERROR(DECODER,   "decoder");
219         DEFINE_ERROR(RENDERER,  "renderer");
220     default:
221         str = "unknown";
222         break;
223     }
224 #undef DEFINE_ERROR
225
226     return str;
227 }
228
229 static void
230 decoder_release(App *app)
231 {
232     g_mutex_lock(&app->mutex);
233     g_cond_signal(&app->decoder_ready);
234     g_mutex_unlock(&app->mutex);
235 }
236
237 static gpointer
238 decoder_thread(gpointer data)
239 {
240     App * const app = data;
241     GError *error = NULL;
242     GstVaapiDecoderStatus status;
243     GstVaapiSurfaceProxy *proxy;
244     RenderFrame *rfp;
245     GstBuffer *buffer;
246     GstClockTime pts;
247     gboolean got_surface, got_eos = FALSE;
248     gint64 end_time;
249     guint ofs;
250
251     g_print("Decoder thread started\n");
252
253 #define SEND_ERROR(...)                                                 \
254     do {                                                                \
255         error = g_error_new(APP_ERROR, APP_ERROR_DECODER, __VA_ARGS__); \
256         goto send_error;                                                \
257     } while (0)
258
259     pts = g_get_monotonic_time();
260     ofs = 0;
261     while (!app->decoder_thread_cancel) {
262         if (G_UNLIKELY(ofs == app->file_size))
263             buffer = NULL;
264         else {
265             const gsize size = MIN(4096, app->file_size - ofs);
266             buffer = gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_READONLY,
267                 app->file_data, app->file_size, ofs, size, NULL, NULL);
268             if (!buffer)
269                 SEND_ERROR("failed to allocate new buffer");
270             ofs += size;
271         }
272         if (!gst_vaapi_decoder_put_buffer(app->decoder, buffer))
273             SEND_ERROR("failed to push buffer to decoder");
274         gst_buffer_replace(&buffer, NULL);
275
276     get_surface:
277         status = gst_vaapi_decoder_get_surface(app->decoder, &proxy);
278         switch (status) {
279         case GST_VAAPI_DECODER_STATUS_SUCCESS:
280             gst_vaapi_surface_proxy_set_destroy_notify(proxy,
281                 (GDestroyNotify)decoder_release, app);
282             rfp = render_frame_new();
283             if (!rfp)
284                 SEND_ERROR("failed to allocate render frame");
285             rfp->proxy = proxy;
286             rfp->pts = pts;
287             rfp->duration = app->frame_duration;
288             pts += app->frame_duration;
289             g_async_queue_push(app->decoder_queue, rfp);
290             break;
291         case GST_VAAPI_DECODER_STATUS_ERROR_NO_DATA:
292             /* nothing to do, just continue to the next iteration */
293             break;
294         case GST_VAAPI_DECODER_STATUS_END_OF_STREAM:
295             gst_vaapi_decoder_flush(app->decoder);
296             if (got_eos)
297                 goto send_eos;
298             got_eos = TRUE;
299             break;
300         case GST_VAAPI_DECODER_STATUS_ERROR_NO_SURFACE:
301             end_time = g_get_monotonic_time() + G_TIME_SPAN_SECOND;
302             g_mutex_lock(&app->mutex);
303             got_surface = g_cond_wait_until(&app->decoder_ready, &app->mutex,
304                 end_time);
305             g_mutex_unlock(&app->mutex);
306             if (got_surface)
307                 goto get_surface;
308             SEND_ERROR("failed to acquire a surface within one second");
309             break;
310         default:
311             SEND_ERROR("%s", get_decoder_status_string(status));
312             break;
313         }
314     }
315     return NULL;
316
317 #undef SEND_ERROR
318
319 send_eos:
320     app_send_eos(app);
321     return NULL;
322
323 send_error:
324     app_send_error(app, error);
325     return NULL;
326 }
327
328 static void
329 app_set_framerate(App *app, guint fps_n, guint fps_d)
330 {
331     if (!fps_n || !fps_d)
332         return;
333
334     g_mutex_lock(&app->mutex);
335     if (fps_n != app->fps_n || fps_d != app->fps_d) {
336         app->fps_n = fps_n;
337         app->fps_d = fps_d;
338         app->frame_duration = gst_util_uint64_scale(
339             GST_TIME_AS_USECONDS(GST_SECOND), fps_d, fps_n);
340     }
341     g_mutex_unlock(&app->mutex);
342 }
343
344 static void
345 handle_decoder_state_changes(GstVaapiDecoder *decoder,
346     const GstVideoCodecState *codec_state, gpointer user_data)
347 {
348     App * const app = user_data;
349
350     g_assert(app->decoder == decoder);
351     app_set_framerate(app, codec_state->info.fps_n, codec_state->info.fps_d);
352 }
353
354 static gboolean
355 start_decoder(App *app)
356 {
357     GstCaps *caps;
358
359     app->file = g_mapped_file_new(app->file_name, FALSE, NULL);
360     if (!app->file)
361         return FALSE;
362
363     app->file_size = g_mapped_file_get_length(app->file);
364     app->file_data = (guint8 *)g_mapped_file_get_contents(app->file);
365     if (!app->file_data)
366         return FALSE;
367
368     caps = caps_from_codec(app->codec);
369     switch (app->codec) {
370     case GST_VAAPI_CODEC_H264:
371         app->decoder = gst_vaapi_decoder_h264_new(app->display, caps);
372         break;
373 #if USE_JPEG_DECODER
374     case GST_VAAPI_CODEC_JPEG:
375         app->decoder = gst_vaapi_decoder_jpeg_new(app->display, caps);
376         break;
377 #endif
378     case GST_VAAPI_CODEC_MPEG2:
379         app->decoder = gst_vaapi_decoder_mpeg2_new(app->display, caps);
380         break;
381     case GST_VAAPI_CODEC_MPEG4:
382         app->decoder = gst_vaapi_decoder_mpeg4_new(app->display, caps);
383         break;
384     case GST_VAAPI_CODEC_VC1:
385         app->decoder = gst_vaapi_decoder_vc1_new(app->display, caps);
386         break;
387     default:
388         app->decoder = NULL;
389         break;
390     }
391     if (!app->decoder)
392         return FALSE;
393
394     gst_vaapi_decoder_set_codec_state_changed_func(app->decoder,
395         handle_decoder_state_changes, app);
396
397     g_timer_start(app->timer);
398
399     app->decoder_thread = g_thread_try_new("Decoder Thread", decoder_thread,
400         app, NULL);
401     if (!app->decoder_thread)
402         return FALSE;
403     return TRUE;
404 }
405
406 static gboolean
407 stop_decoder(App *app)
408 {
409     g_timer_stop(app->timer);
410
411     app->decoder_thread_cancel = TRUE;
412     g_thread_join(app->decoder_thread);
413     g_print("Decoder thread stopped\n");
414     return TRUE;
415 }
416
417 static void
418 ensure_window_size(App *app, GstVaapiSurface *surface)
419 {
420     guint width, height;
421
422     if (gst_vaapi_window_get_fullscreen(app->window))
423         return;
424
425     gst_vaapi_surface_get_size(surface, &width, &height);
426     if (app->surface_width == width && app->surface_height == height)
427         return;
428     app->surface_width = width;
429     app->surface_height = height;
430
431     gst_vaapi_window_set_size(app->window, width, height);
432     gst_vaapi_window_get_size(app->window,
433         &app->window_width, &app->window_height);
434 }
435
436 static gboolean
437 ensure_pixmaps(App *app, GstVaapiSurface *surface,
438     const GstVaapiRectangle *crop_rect)
439 {
440     GstVaapiPixmap *pixmaps[G_N_ELEMENTS(app->pixmaps)];
441     guint num_pixmaps, i, width, height;
442     gboolean success = FALSE;
443
444     if (crop_rect) {
445         width  = crop_rect->width;
446         height = crop_rect->height;
447     }
448     else
449         gst_vaapi_surface_get_size(surface, &width, &height);
450     if (app->pixmap_width == width && app->pixmap_height == height)
451         return TRUE;
452
453     for (i = 0, num_pixmaps = 0; i < G_N_ELEMENTS(pixmaps); i++) {
454         GstVaapiPixmap * const pixmap =
455             video_output_create_pixmap(app->display, GST_VIDEO_FORMAT_xRGB,
456                 width, height);
457         if (!pixmap)
458             goto end;
459         pixmaps[num_pixmaps++] = pixmap;
460     }
461
462     for (i = 0; i < num_pixmaps; i++)
463         gst_vaapi_pixmap_replace(&app->pixmaps[i], pixmaps[i]);
464     app->pixmap_width = width;
465     app->pixmap_height = height;
466     success = TRUE;
467
468 end:
469     for (i = 0; i < num_pixmaps; i++)
470         gst_vaapi_pixmap_replace(&pixmaps[i], NULL);
471     return success;
472 }
473
474 static inline void
475 renderer_wait_until(App *app, GstClockTime pts)
476 {
477     g_mutex_lock(&app->mutex);
478     do {
479     } while (g_cond_wait_until(&app->render_ready, &app->mutex, pts));
480     g_mutex_unlock(&app->mutex);
481 }
482
483 static gboolean
484 renderer_process(App *app, RenderFrame *rfp)
485 {
486     GError *error = NULL;
487     GstVaapiSurface *surface;
488     const GstVaapiRectangle *crop_rect;
489
490 #define SEND_ERROR(...)                                                 \
491     do {                                                                \
492         error = g_error_new(APP_ERROR, APP_ERROR_RENDERER, __VA_ARGS__); \
493         goto send_error;                                                \
494     } while (0)
495
496     surface = gst_vaapi_surface_proxy_get_surface(rfp->proxy);
497     if (!surface)
498         SEND_ERROR("failed to get decoded surface from render frame");
499
500     ensure_window_size(app, surface);
501
502     crop_rect = gst_vaapi_surface_proxy_get_crop_rect(rfp->proxy);
503     if (g_use_pixmap && !ensure_pixmaps(app, surface, crop_rect))
504         SEND_ERROR("failed to create intermediate pixmaps");
505
506     if (!gst_vaapi_surface_sync(surface))
507         SEND_ERROR("failed to sync decoded surface");
508
509     if (G_LIKELY(!g_benchmark))
510         renderer_wait_until(app, rfp->pts);
511
512     if (G_UNLIKELY(g_use_pixmap)) {
513         GstVaapiPixmap * const pixmap = app->pixmaps[app->pixmap_id];
514
515         if (!gst_vaapi_pixmap_put_surface(pixmap, surface, crop_rect,
516                 GST_VAAPI_PICTURE_STRUCTURE_FRAME))
517             SEND_ERROR("failed to render to pixmap");
518
519         if (!gst_vaapi_window_put_pixmap(app->window, pixmap, NULL, NULL))
520             SEND_ERROR("failed to render surface %" GST_VAAPI_ID_FORMAT,
521                        GST_VAAPI_ID_ARGS(pixmap));
522
523         app->pixmap_id = (app->pixmap_id + 1) % G_N_ELEMENTS(app->pixmaps);
524     }
525     else if (!gst_vaapi_window_put_surface(app->window, surface,
526                  crop_rect, NULL, GST_VAAPI_PICTURE_STRUCTURE_FRAME))
527         SEND_ERROR("failed to render surface %" GST_VAAPI_ID_FORMAT,
528                    GST_VAAPI_ID_ARGS(gst_vaapi_surface_get_id(surface)));
529
530     app->num_frames++;
531
532     render_frame_replace(&app->last_frame, rfp);
533     return TRUE;
534
535 #undef SEND_ERROR
536
537 send_error:
538     app_send_error(app, error);
539     return FALSE;
540 }
541
542 static gpointer
543 renderer_thread(gpointer data)
544 {
545     App * const app = data;
546     RenderFrame *rfp;
547
548     g_print("Render thread started\n");
549
550     while (!app->render_thread_cancel) {
551         rfp = g_async_queue_timeout_pop(app->decoder_queue, 1000000);
552         if (rfp && !renderer_process(app, rfp))
553             break;
554     }
555     return NULL;
556 }
557
558 static gboolean
559 flush_decoder_queue(App *app)
560 {
561     RenderFrame *rfp;
562
563     /* Flush pending surfaces */
564     do {
565         rfp = g_async_queue_try_pop(app->decoder_queue);
566         if (!rfp)
567             return TRUE;
568     } while (renderer_process(app, rfp));
569     return FALSE;
570 }
571
572 static gboolean
573 start_renderer(App *app)
574 {
575     app->render_thread = g_thread_try_new("Renderer Thread", renderer_thread,
576         app, NULL);
577     if (!app->render_thread)
578         return FALSE;
579     return TRUE;
580 }
581
582 static gboolean
583 stop_renderer(App *app)
584 {
585     app->render_thread_cancel = TRUE;
586     g_thread_join(app->render_thread);
587
588     g_print("Render thread stopped\n");
589
590     flush_decoder_queue(app);
591     render_frame_replace(&app->last_frame, NULL);
592     return TRUE;
593 }
594
595 static void
596 app_free(App *app)
597 {
598     guint i;
599
600     if (!app)
601         return;
602
603     if (app->file) {
604         g_mapped_file_unref(app->file);
605         app->file = NULL;
606     }
607     g_free(app->file_name);
608
609     for (i = 0; i < G_N_ELEMENTS(app->pixmaps); i++)
610         gst_vaapi_pixmap_replace(&app->pixmaps[i], NULL);
611     gst_vaapi_decoder_replace(&app->decoder, NULL);
612     gst_vaapi_window_replace(&app->window, NULL);
613     gst_vaapi_display_replace(&app->display, NULL);
614
615     if (app->decoder_queue) {
616         g_async_queue_unref(app->decoder_queue);
617         app->decoder_queue = NULL;
618     }
619     g_cond_clear(&app->decoder_ready);
620
621     if (app->timer) {
622         g_timer_destroy(app->timer);
623         app->timer = NULL;
624     }
625
626     g_cond_clear(&app->render_ready);
627     g_cond_clear(&app->event_cond);
628     g_mutex_clear(&app->mutex);
629     g_slice_free(App, app);
630 }
631
632 static App *
633 app_new(void)
634 {
635     App *app;
636
637     app = g_slice_new0(App);
638     if (!app)
639         return NULL;
640
641     g_mutex_init(&app->mutex);
642     g_cond_init(&app->event_cond);
643     g_cond_init(&app->decoder_ready);
644     g_cond_init(&app->render_ready);
645
646     app_set_framerate(app, 60, 1);
647     app->window_width = 640;
648     app->window_height = 480;
649
650     app->decoder_queue = g_async_queue_new_full(
651         (GDestroyNotify)render_frame_free);
652     if (!app->decoder_queue)
653         goto error;
654
655     app->timer = g_timer_new();
656     if (!app->timer)
657         goto error;
658     return app;
659
660 error:
661     app_free(app);
662     return NULL;
663 }
664
665 static gboolean
666 app_check_events(App *app)
667 {
668     GError *error = NULL;
669     gboolean stop = FALSE;
670
671     do {
672         g_mutex_lock(&app->mutex);
673         while (app->event == APP_RUNNING)
674             g_cond_wait(&app->event_cond, &app->mutex);
675
676         switch (app->event) {
677         case APP_GOT_ERROR:
678             error = app->error;
679             app->error = NULL;
680             /* fall-through */
681         case APP_GOT_EOS:
682             stop = TRUE;
683             break;
684         default:
685             break;
686         }
687         g_mutex_unlock(&app->mutex);
688     } while (!stop);
689
690     if (!error)
691         return TRUE;
692
693     g_message("%s error: %s", get_error_string(error->code), error->message);
694     g_error_free(error);
695     return FALSE;
696 }
697
698 static gboolean
699 app_run(App *app, int argc, char *argv[])
700 {
701     if (argc < 2) {
702         g_message("no bitstream file specified");
703         return FALSE;
704     }
705     app->file_name = g_strdup(argv[1]);
706
707     if (!g_file_test(app->file_name, G_FILE_TEST_IS_REGULAR)) {
708         g_message("failed to find file '%s'", app->file_name);
709         return FALSE;
710     }
711
712     app->codec = identify_codec(app->file_name);
713     if (!app->codec) {
714         app->codec = identify_codec_from_string(g_codec_str);
715         if (!app->codec) {
716             g_message("failed to identify codec for '%s'", app->file_name);
717             return FALSE;
718         }
719     }
720
721     g_print("Simple decoder (%s bitstream)\n", string_from_codec(app->codec));
722
723     app->display = video_output_create_display(NULL);
724     if (!app->display) {
725         g_message("failed to create VA display");
726         return FALSE;
727     }
728
729     app->window = video_output_create_window(app->display,
730         app->window_width, app->window_height);
731     if (!app->window) {
732         g_message("failed to create window");
733         return FALSE;
734     }
735
736     gst_vaapi_window_show(app->window);
737
738     if (!start_decoder(app)) {
739         g_message("failed to start decoder thread");
740         return FALSE;
741     }
742
743     if (!start_renderer(app)) {
744         g_message("failed to start renderer thread");
745         return FALSE;
746     }
747
748     app_check_events(app);
749
750     stop_renderer(app);
751     stop_decoder(app);
752
753     g_print("Decoded %u frames", app->num_frames);
754     if (g_benchmark) {
755         const gdouble elapsed = g_timer_elapsed(app->timer, NULL);
756         g_print(" in %.2f sec (%.1f fps)\n",
757                 elapsed, (gdouble)app->num_frames / elapsed);
758     }
759     g_print("\n");
760     return TRUE;
761 }
762
763 int
764 main(int argc, char *argv[])
765 {
766     App *app;
767     gint ret;
768
769     if (!video_output_init(&argc, argv, g_options))
770         g_error("failed to initialize video output subsystem");
771
772     app = app_new();
773     if (!app)
774         g_error("failed to create application context");
775
776     ret = !app_run(app, argc, argv);
777
778     app_free(app);
779     g_free(g_codec_str);
780     video_output_exit();
781     return ret;
782 }