[AD] video addon

[ Thread Index | Date Index | More lists.liballeg.org/allegro-developers Archives ]


I wrote this a few years ago and had it sitting in a local git branch
since then, always wanting to fix a few things and replace ffmpeg with
libtheora. But doesn't look like I'll get around to anytime soon, so
sending it here anyway. It uses ffmpeg to render video frames into an
ALLEGRO_BITMAP and works under Windows, OSX and Linux. It doesn't work
very well but should be enough to display a short intro video clip for
a game - so maybe it can be useful to someone.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 72fabbf..4b41322 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -120,6 +120,7 @@ option(WANT_PHYSFS "Enable PhysicsFS addon" on)
 option(WANT_PRIMITIVES "Enable primitives addon" on)
 option(WANT_NATIVE_DIALOG "Enable native dialog addon" on)
 option(WANT_SHADERS "Enable shader addon" on)
+option(WANT_VIDEO "Enable video player addon" on)
 
 #
 # Wrappers.
@@ -878,6 +879,7 @@ set(PKG_CONFIG_FILES
     allegro_physfs
     allegro_primitives
     allegro_ttf
+    allegro_video
     )
 
 # Probably usable on Windows as well but we shouldn't install it
diff --git a/addons/CMakeLists.txt b/addons/CMakeLists.txt
index c1a45ea..c76ab42 100644
--- a/addons/CMakeLists.txt
+++ b/addons/CMakeLists.txt
@@ -129,6 +129,12 @@ if(WANT_SHADERS)
     set(ALLEGRO_LINK_WITH ${ALLEGRO_LINK_WITH} PARENT_SCOPE)
 endif(WANT_SHADERS)
 
+if(WANT_VIDEO)
+    add_subdirectory(video)
+    set(SUPPORT_VIDEO ${SUPPORT_VIDEO} PARENT_SCOPE)
+    set(VIDEO_LINK_WITH ${VIDEO_LINK_WITH} PARENT_SCOPE)
+endif(WANT_VIDEO)
+
 add_subdirectory(main)
 set(SUPPORT_ALLEGRO_MAIN ${SUPPORT_ALLEGRO_MAIN} PARENT_SCOPE)
 set(ALLEGRO_MAIN_LINK_WITH ${ALLEGRO_MAIN_LINK_WITH} PARENT_SCOPE)
diff --git a/addons/video/CMakeLists.txt b/addons/video/CMakeLists.txt
new file mode 100644
index 0000000..e49f58b
--- /dev/null
+++ b/addons/video/CMakeLists.txt
@@ -0,0 +1,52 @@
+option(WANT_FFMPEG "Enable FFMPEG video driver" on)
+
+set(VIDEO_SOURCES
+    video.c
+    )
+
+set(VIDEO_INCLUDE_FILES allegro5/allegro_video.h)
+
+include_directories(../audio ../image .)
+
+set_our_header_properties(${AUDIO_INCLUDE_FILES})
+
+# The platform conditions are not really necessary but prevent confusing the
+# user, e.g. it's pretty weird to get a warning about missing DSound on Unix.
+
+if(WANT_FFMPEG)
+    include(AllegroFindFFMPEG)
+    if(FFMPEG_FOUND)
+        set(SUPPORT_FFMPEG 1)
+    endif(FFMPEG_FOUND)
+endif(WANT_FFMPEG)
+
+if(SUPPORT_FFMPEG)
+    set(ALLEGRO_CFG_VIDEO_FFMPEG 1)
+    list(APPEND VIDEO_SOURCES ffmpeg.c)
+    set(SUPPORT_VIDEO 1)
+    set(VIDEO_LIBRARIES ${FFMPEG_LIBRARIES})
+endif(SUPPORT_FFMPEG)
+
+if(NOT SUPPORT_VIDEO)
+    message("WARNING: allegro_video wanted but no supported backend found")
+    return()
+endif(NOT SUPPORT_VIDEO)
+
+# Let examples know that video is supported.
+set(SUPPORT_VIDEO 1 PARENT_SCOPE)
+
+add_our_library(allegro_video
+    "${VIDEO_SOURCES};${VIDEO_INCLUDE_FILES}"
+    "-DALLEGRO_VIDEO_SRC"
+    "${ALLEGRO_LINK_WITH};${VIDEO_LIBRARIES}"
+    )
+
+set_our_framework_properties(allegro_video AllegroVideo-${ALLEGRO_SOVERSION})
+
+install_our_library(allegro_video)
+install_our_headers(${VIDEO_INCLUDE_FILES})
+
+set(VIDEO_LINK_WITH allegro_video PARENT_SCOPE)
+
+#-----------------------------------------------------------------------------#
+# vi: set ts=8 sts=4 sw=4 et:
diff --git a/addons/video/allegro5/allegro_video.h b/addons/video/allegro5/allegro_video.h
new file mode 100644
index 0000000..f8be655
--- /dev/null
+++ b/addons/video/allegro5/allegro_video.h
@@ -0,0 +1,66 @@
+#ifndef __al_included_allegro_video_h
+#define __al_included_allegro_video_h
+
+#ifdef __cplusplus
+   extern "C" {
+#endif
+
+#include "allegro5/allegro5.h"
+#include "allegro5/allegro_audio.h"
+
+#if (defined ALLEGRO_MINGW32) || (defined ALLEGRO_MSVC) || (defined ALLEGRO_BCC32)
+   #ifndef ALLEGRO_STATICLINK
+      #ifdef ALLEGRO_VIDEO_SRC
+         #define _ALLEGRO_VIDEO_DLL __declspec(dllexport)
+      #else
+         #define _ALLEGRO_VIDEO_DLL __declspec(dllimport)
+      #endif
+   #else
+      #define _ALLEGRO_VIDEO_DLL
+   #endif
+#endif
+
+#if defined ALLEGRO_MSVC
+   #define ALLEGRO_VIDEO_FUNC(type, name, args)      _ALLEGRO_VIDEO_DLL type __cdecl name args
+#elif defined ALLEGRO_MINGW32
+   #define ALLEGRO_VIDEO_FUNC(type, name, args)      extern type name args
+#elif defined ALLEGRO_BCC32
+   #define ALLEGRO_VIDEO_FUNC(type, name, args)      extern _ALLEGRO_VIDEO_DLL type name args
+#else
+   #define ALLEGRO_VIDEO_FUNC      AL_FUNC
+#endif
+
+// FIXME: might collide with another addon, but i forgot what the
+// convention is. Maybe should be using ALLEGRO_ID here?
+
+/* Enum: ALLEGRO_VIDEO_EVENT_TYPE
+ */
+enum ALLEGRO_VIDEO_EVENT_TYPE
+{
+   ALLEGRO_EVENT_VIDEO_FRAME_ALLOC       = 600,
+   ALLEGRO_EVENT_VIDEO_FRAME_SHOW       = 601,
+};
+
+typedef struct ALLEGRO_VIDEO ALLEGRO_VIDEO;
+
+ALLEGRO_VIDEO_FUNC(ALLEGRO_VIDEO *, al_open_video, (char const *filename));
+ALLEGRO_VIDEO_FUNC(void, al_close_video, (ALLEGRO_VIDEO *video));
+ALLEGRO_VIDEO_FUNC(void, al_start_video, (ALLEGRO_VIDEO *video, ALLEGRO_MIXER *mixer));
+ALLEGRO_VIDEO_FUNC(void, al_start_video_with_voice, (ALLEGRO_VIDEO *video, ALLEGRO_VOICE *voice));
+ALLEGRO_VIDEO_FUNC(ALLEGRO_EVENT_SOURCE *, al_get_video_event_source, (ALLEGRO_VIDEO *video));
+ALLEGRO_VIDEO_FUNC(void, al_pause_video, (ALLEGRO_VIDEO *video, bool paused));
+ALLEGRO_VIDEO_FUNC(bool, al_is_video_paused, (ALLEGRO_VIDEO *video));
+ALLEGRO_VIDEO_FUNC(double, al_get_video_aspect_ratio, (ALLEGRO_VIDEO *video));
+ALLEGRO_VIDEO_FUNC(double, al_get_video_audio_rate, (ALLEGRO_VIDEO *video));
+ALLEGRO_VIDEO_FUNC(double, al_get_video_fps, (ALLEGRO_VIDEO *video));
+ALLEGRO_VIDEO_FUNC(int, al_get_video_width, (ALLEGRO_VIDEO *video));
+ALLEGRO_VIDEO_FUNC(int, al_get_video_height, (ALLEGRO_VIDEO *video));
+ALLEGRO_VIDEO_FUNC(ALLEGRO_BITMAP *, al_get_video_frame, (ALLEGRO_VIDEO *video));
+ALLEGRO_VIDEO_FUNC(double, al_get_video_position, (ALLEGRO_VIDEO *video, int which));
+ALLEGRO_VIDEO_FUNC(void, al_seek_video, (ALLEGRO_VIDEO *video, double pos_in_seconds));
+
+#ifdef __cplusplus
+   }
+#endif
+
+#endif
diff --git a/addons/video/allegro5/internal/aintern_video.h b/addons/video/allegro5/internal/aintern_video.h
new file mode 100644
index 0000000..7cee3f2
--- /dev/null
+++ b/addons/video/allegro5/internal/aintern_video.h
@@ -0,0 +1,42 @@
+
+typedef struct ALLEGRO_VIDEO_INTERFACE {
+   bool (*open_video)(ALLEGRO_VIDEO *video);
+   bool (*close_video)(ALLEGRO_VIDEO *video);
+   bool (*start_video)(ALLEGRO_VIDEO *video);
+   bool (*pause_video)(ALLEGRO_VIDEO *video);
+   bool (*seek_video)(ALLEGRO_VIDEO *video);
+   bool (*update_video)(ALLEGRO_VIDEO *video);
+} ALLEGRO_VIDEO_INTERFACE;
+
+struct ALLEGRO_VIDEO {
+   ALLEGRO_VIDEO_INTERFACE *vtable;
+   
+   /* video */
+   ALLEGRO_BITMAP *current_frame;
+   double video_position;
+   double fps;
+   int width, height;
+   
+   /* audio */
+   ALLEGRO_MIXER *mixer;
+   ALLEGRO_VOICE *voice;
+   int audio_buffer_count;
+   int audio_samples_per_buffer;
+   ALLEGRO_AUDIO_STREAM *audio;
+   double audio_position;
+   double audio_rate;
+
+   /* general */
+   ALLEGRO_EVENT_SOURCE es;
+   ALLEGRO_PATH *filename;
+   bool paused;
+   double audio_offset;
+   double position;
+   double seek_to;
+   double aspect_ratio;
+
+   /* implementation specific */
+   void *data;
+};
+
+extern ALLEGRO_VIDEO_INTERFACE *_al_video_vtable;
diff --git a/addons/video/ffmpeg.c b/addons/video/ffmpeg.c
new file mode 100644
index 0000000..028d310
--- /dev/null
+++ b/addons/video/ffmpeg.c
@@ -0,0 +1,1201 @@
+/* TODO: Thsi is just a quick ffmpeg video player test. Eventually this
+ * doesn't have to be ffmpeg only though, could also use things like
+ * theora.
+ * 
+ * Timing:
+ * The video is always timed off wall clock time.
+ * 
+ * Video:
+ * For each frame, we have its presentation time stamp (pts). That is
+ * the time at which it should be displayed. When we get our first
+ * frame, we can check its pts and also look at the clock. Then when
+ * the next frame appears, we can use the difference of its pts and the
+ * pts of the first frame to know exactly at which time we should
+ * display it.
+ * 
+ * t1 = t0 + (pts1 - pts0)
+ * t2 = t0 + (pts2 - pts0)
+ * ...
+ * 
+ * Audio:
+ * Audio frames also have a pts. So just like with video frames we know
+ * when audio should be played. It's a bit more difficult here since
+ * we don't know precisely when the audio data we put in the
+ * audio stream buffer are sent to the soundcard.
+ * 
+ * TODO: al_get_audio_stream_fragments actually might be useful for
+ * estimating the delay.
+ * 
+ */
+
+#include "allegro5/allegro5.h"
+#include "allegro5/allegro_audio.h"
+#include "allegro5/allegro_video.h"
+#include "allegro5/internal/aintern_video.h"
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+
+#include <allegro5/allegro.h>
+#include <allegro5/allegro_audio.h>
+#include <allegro5/allegro_image.h>
+//#include <allegro5/allegro_font.h>
+//#include <allegro5/allegro_primitives.h>
+#include <allegro5/allegro_video.h>
+#include <stdio.h>
+#include <math.h>
+
+ALLEGRO_DEBUG_CHANNEL("video")
+
+#define SDL_AUDIO_BUFFER_SIZE (1024 * 8)
+#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
+#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)
+#define AV_SYNC_THRESHOLD 0.10
+#define AV_NOSYNC_THRESHOLD 10.0
+#define SAMPLE_CORRECTION_PERCENT_MAX 10
+#define AUDIO_DIFF_AVG_NB 20
+#define VIDEO_PICTURE_QUEUE_SIZE 3
+#define DEFAULT_AV_SYNC_TYPE AV_SYNC_EXTERNAL_MASTER
+#define AUDIO_BUF_SIZE ((AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2)
+#define AUDIO_BUF(vs) ((uint8_t *)((intptr_t)((vs)->audio_buf_unaligned + 15) & ~0xf))
+
+#define NOPTS_VALUE ((int64_t)AV_NOPTS_VALUE)
+int64_t FIXME_global_video_pkt_pts = NOPTS_VALUE;
+
+typedef struct PacketQueue {
+   AVPacketList *first_pkt, *last_pkt;
+   int nb_packets;
+   int size;
+   ALLEGRO_MUTEX *mutex;
+   ALLEGRO_COND *cond;
+} PacketQueue;
+
+typedef struct VideoPicture {
+   AVFrame *frame;
+   ALLEGRO_BITMAP *bmp;
+   int width, height;          
+   int allocated;
+   double pts;
+   bool dropped;
+} VideoPicture;
+
+typedef struct VideoState {
+   ALLEGRO_VIDEO *video;
+   ALLEGRO_MUTEX *pictq_mutex;
+   ALLEGRO_COND *pictq_cond;
+   ALLEGRO_THREAD *parse_thread;
+   ALLEGRO_THREAD *video_thread;
+   ALLEGRO_THREAD *audio_thread;
+   
+   /* Timing. */
+   ALLEGRO_THREAD *timer_thread;
+   ALLEGRO_MUTEX *timer_mutex;
+   ALLEGRO_COND *timer_cond;
+
+   AVFormatContext *format_context;
+   int videoStream, audioStream;
+   int video_index, audio_index;
+
+   int av_sync_type;
+   int seek_req;
+   int seek_flags;
+   bool after_seek_sync;
+   int64_t seek_pos;
+   double audio_clock;
+   AVStream *audio_st;
+   PacketQueue audioq;
+   uint8_t audio_buf_unaligned[15 + AUDIO_BUF_SIZE];
+   unsigned int audio_buf_size;
+   unsigned int audio_buf_index;
+   AVPacket audio_pkt;
+   uint8_t *audio_pkt_data;
+   int audio_pkt_size;
+   int audio_hw_buf_size;
+   double audio_diff_cum;
+   double audio_diff_avg_coef;
+   double audio_diff_threshold;
+   int audio_diff_avg_count;
+   double frame_timer;
+   double frame_last_pts;
+   double frame_last_delay;
+   double video_clock;      
+   double video_current_pts;    
+   int64_t video_current_pts_time;
+   int64_t external_clock_start;
+   AVStream *video_st;
+   PacketQueue videoq;
+   
+   double show_next;
+   bool first;
+   int got_picture;
+   int dropped_count;
+   bool paused;
+
+   VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
+   VideoPicture shown;
+   int pictq_size, pictq_rindex, pictq_windex;
+   char filename[1024];
+   bool quit;
+} VideoState;
+
+enum
+{
+   AV_SYNC_AUDIO_MASTER,
+   AV_SYNC_VIDEO_MASTER,
+   AV_SYNC_EXTERNAL_MASTER,
+};
+
+static AVPacket flush_pkt;
+static bool init_once;
+
+static void packet_queue_init(PacketQueue * q)
+{
+   memset(q, 0, sizeof(PacketQueue));
+   q->mutex = al_create_mutex();
+   q->cond = al_create_cond();
+}
+
+static int packet_queue_put(PacketQueue * q, AVPacket * pkt)
+{
+   AVPacketList *pkt1;
+   if (pkt != &flush_pkt && av_dup_packet(pkt) < 0) {
+      return -1;
+   }
+   pkt1 = av_malloc(sizeof(AVPacketList));
+   if (!pkt1)
+      return -1;
+   pkt1->pkt = *pkt;
+   pkt1->next = NULL;
+
+   al_lock_mutex(q->mutex);
+
+   if (!q->last_pkt)
+      q->first_pkt = pkt1;
+   else
+      q->last_pkt->next = pkt1;
+   q->last_pkt = pkt1;
+   q->nb_packets++;
+   q->size += pkt1->pkt.size;
+   al_signal_cond(q->cond);
+   al_unlock_mutex(q->mutex);
+   return 0;
+}
+
+static int packet_queue_get(VideoState *is, PacketQueue * q, AVPacket * pkt, int block)
+{
+   AVPacketList *pkt1;
+   int ret;
+
+   al_lock_mutex(q->mutex);
+
+   for (;;) {
+
+      if (is->quit) {
+         ret = -1;
+         break;
+      }
+
+      pkt1 = q->first_pkt;
+      if (pkt1) {
+         q->first_pkt = pkt1->next;
+         if (!q->first_pkt)
+            q->last_pkt = NULL;
+         q->nb_packets--;
+         q->size -= pkt1->pkt.size;
+         *pkt = pkt1->pkt;
+         av_free(pkt1);
+         ret = 1;
+         break;
+      }
+      else if (!block) {
+         ret = 0;
+         break;
+      }
+      else {
+         al_wait_cond(q->cond, q->mutex);
+      }
+   }
+   al_unlock_mutex(q->mutex);
+   return ret;
+}
+
+static void packet_queue_flush(PacketQueue * q)
+{
+   AVPacketList *pkt, *pkt1;
+
+   al_lock_mutex(q->mutex);
+   for (pkt = q->first_pkt; pkt != NULL; pkt = pkt1) {
+      pkt1 = pkt->next;
+      av_free_packet(&pkt->pkt);
+      av_freep(&pkt);
+   }
+   q->last_pkt = NULL;
+   q->first_pkt = NULL;
+   q->nb_packets = 0;
+   q->size = 0;
+   al_unlock_mutex(q->mutex);
+}
+
+static double get_audio_clock(VideoState *is)
+{
+   double pts;
+   int hw_buf_size, bytes_per_sec, n;
+
+   pts = is->audio_clock;       /* maintained in the audio thread */
+   hw_buf_size = is->audio_buf_size - is->audio_buf_index;
+   bytes_per_sec = 0;
+   n = is->audio_st->codec->channels * 2;
+   if (is->audio_st) {
+      bytes_per_sec = is->audio_st->codec->sample_rate * n;
+   }
+   if (bytes_per_sec) {
+      pts -= (double)hw_buf_size / bytes_per_sec;
+   }
+   return pts;
+}
+
+static double get_video_clock(VideoState * is)
+{
+   double delta;
+
+   delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;
+   return is->video_current_pts + delta;
+}
+
+static double get_external_clock(VideoState * is)
+{
+   return (av_gettime() - is->external_clock_start) / 1000000.0;
+}
+
+static double get_master_clock(VideoState * is)
+{
+   if (is->av_sync_type == AV_SYNC_VIDEO_MASTER) {
+      return get_video_clock(is);
+   }
+   else if (is->av_sync_type == AV_SYNC_AUDIO_MASTER) {
+      return get_audio_clock(is);
+   }
+   else {
+      return get_external_clock(is);
+   }
+}
+
+/* Add or subtract samples to get a better sync, return new
+   audio buffer size */
+static int synchronize_audio(VideoState * is, short *samples,
+                      int samples_size, double pts)
+{
+   int n;
+   double ref_clock;
+   (void)pts;
+   double diff;
+
+   if (is->after_seek_sync) {
+      /* Audio seems to be off for me after seeking, but skipping
+       * video is less annoying than audio noise after the seek
+       * when synching to the external clock.
+       */
+      is->external_clock_start = av_gettime() - get_audio_clock(is) * 1000000.0;
+      is->after_seek_sync = false;
+   }
+
+   n = 2 * is->audio_st->codec->channels;
+   
+   if (is->av_sync_type != AV_SYNC_AUDIO_MASTER) {
+      double avg_diff;
+      int wanted_size, min_size, max_size;
+      
+      ref_clock = get_master_clock(is);
+      diff = get_audio_clock(is) - ref_clock;
+      
+      //printf("%f, %f\n", diff, get_audio_clock(is));
+
+      if (diff < AV_NOSYNC_THRESHOLD) {
+         // accumulate the diffs
+         is->audio_diff_cum = diff + is->audio_diff_avg_coef
+             * is->audio_diff_cum;
+         if (is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {
+            is->audio_diff_avg_count++;
+         }
+         else {
+            avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);
+            if (fabs(avg_diff) >= is->audio_diff_threshold) {
+               //printf("AV_NOSYNC_THRESHOLD %f\n", avg_diff);
+               wanted_size =
+                   samples_size +
+                   ((int)(avg_diff * is->audio_st->codec->sample_rate) * n);
+               min_size = samples_size * (100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100;
+               min_size &= ~3;
+               max_size = samples_size * (100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100;
+               max_size &= ~3;
+
+               if (wanted_size < min_size) {
+                  wanted_size = min_size;
+               }
+               else if (wanted_size > max_size) {
+                  wanted_size = max_size;
+               }
+               if (wanted_size < samples_size) {
+                  /* remove samples */
+                  samples_size = wanted_size;
+               }
+               else if (wanted_size > samples_size) {
+                  uint8_t *samples_end, *q;
+                  int nb;
+                  /* add samples by copying final sample */
+                  nb = (samples_size - wanted_size);
+                  samples_end = (uint8_t *) samples + samples_size - n;
+                  q = samples_end + n;
+                  while (nb > 0) {
+                     memcpy(q, samples_end, n);
+                     q += n;
+                     nb -= n;
+                  }
+                  samples_size = wanted_size;
+               }
+            }
+         }
+      }
+      else {
+         /* difference is TOO big; reset diff stuff */
+         is->audio_diff_avg_count = 0;
+         is->audio_diff_cum = 0;
+      }
+   }
+   return samples_size;
+}
+
+static int audio_decode_frame(VideoState * is, uint8_t * audio_buf, int buf_size,
+                       double *pts_ptr)
+{
+   int len1, data_size, n;
+   AVPacket *pkt = &is->audio_pkt;
+   double pts;
+
+   for (;;) {
+      while (is->audio_pkt_size > 0) {
+         data_size = buf_size;
+         len1 = avcodec_decode_audio2(is->audio_st->codec,
+                                      (int16_t *) audio_buf, &data_size,
+                                      is->audio_pkt_data, is->audio_pkt_size);
+         if (len1 < 0) {
+            /* if error, skip frame */
+            is->audio_pkt_size = 0;
+            break;
+         }
+         is->audio_pkt_data += len1;
+         is->audio_pkt_size -= len1;
+         if (data_size <= 0) {
+            /* No data yet, get more frames */
+            continue;
+         }
+         pts = is->audio_clock;
+         *pts_ptr = pts;
+         n = 2 * is->audio_st->codec->channels;
+         is->audio_clock += (double)data_size /
+             (double)(n * is->audio_st->codec->sample_rate);
+
+         /* We have data, return it and come back for more later */
+         return data_size;
+      }
+      if (pkt->data)
+         av_free_packet(pkt);
+
+      if (is->quit) {
+         return -1;
+      }
+      /* next packet */
+      if (packet_queue_get(is, &is->audioq, pkt, 1) < 0) {
+         return -1;
+      }
+      if (pkt->data == flush_pkt.data) {
+         avcodec_flush_buffers(is->audio_st->codec);
+         continue;
+      }
+      is->audio_pkt_data = pkt->data;
+      is->audio_pkt_size = pkt->size;
+      /* if update, update the audio clock w/pts */
+      if (pkt->pts != NOPTS_VALUE) {
+         is->audio_clock = av_q2d(is->audio_st->time_base) * pkt->pts;
+         //printf("audio_clock: %f\n", is->audio_clock);
+      }
+   }
+}
+
+static void audio_callback(void *userdata, uint8_t * stream, int len)
+{
+   VideoState *is = (VideoState *) userdata;
+   int len1, audio_size;
+   double pts;
+   
+   if (!is->first) return;
+
+   while (len > 0) {
+      if (is->audio_buf_index >= is->audio_buf_size) {
+
+         /* We have already sent all our data; get more */
+         audio_size = -1;
+         if (!is->paused) {
+            audio_size = audio_decode_frame(is, AUDIO_BUF(is),
+               AUDIO_BUF_SIZE, &pts);
+         }
+            
+         if (audio_size < 0) {
+            /* If error, output silence */
+            is->audio_buf_size = 1024;
+            memset(AUDIO_BUF(is), 0, is->audio_buf_size);
+         }
+         else {
+            audio_size = synchronize_audio(is, (int16_t *) AUDIO_BUF(is),
+                                           audio_size, pts);
+            is->audio_buf_size = audio_size;
+         }
+         is->audio_buf_index = 0;
+      }
+      len1 = is->audio_buf_size - is->audio_buf_index;
+      if (len1 > len)
+         len1 = len;
+      memcpy(stream, (uint8_t *) AUDIO_BUF(is) + is->audio_buf_index, len1);
+      len -= len1;
+      stream += len1;
+      is->audio_buf_index += len1;
+   }
+}
+
+static void video_refresh_timer(void *userdata)
+{
+
+   VideoState *is = (VideoState *) userdata;
+   VideoPicture *vp;
+   double actual_delay, delay, sync_threshold, ref_clock, diff;
+
+   if (!is->first)
+      return;
+   if (!is->video_st)
+      return;
+   if (is->pictq_size == 0)
+      return;
+   if (is->paused) return;
+   
+   if (get_master_clock(is) < is->show_next) return;
+
+   vp = &is->pictq[is->pictq_rindex];
+
+   is->video_current_pts = vp->pts;
+   is->video_current_pts_time = av_gettime();
+
+   delay = vp->pts - is->frame_last_pts;        /* the pts from last time */
+   if (delay <= 0 || delay >= 1.0) {
+      /* if incorrect delay, use previous one */
+      delay = is->frame_last_delay;
+   }
+   /* save for next time */
+   is->frame_last_delay = delay;
+   is->frame_last_pts = vp->pts;
+
+   /* update delay to sync to audio if not master source */
+   if (is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
+      ref_clock = get_master_clock(is);
+      diff = vp->pts - ref_clock;
+
+      /* Skip or repeat the frame. Take delay into account */
+      sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
+      if (fabs(diff) < AV_NOSYNC_THRESHOLD) {
+         if (diff <= -sync_threshold) {
+            delay = 0;
+         }
+         else if (diff >= sync_threshold) {
+            delay = 2 * delay;
+         }
+      }
+   }
+
+   is->frame_timer += delay;
+   /* computer the REAL delay */
+   actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
+
+   if (!vp->dropped && vp->bmp) {
+      is->video->current_frame = vp->bmp;
+      
+      /* Can be NULL or wrong size, will be (re-)allocated as needed. */
+      vp->bmp = is->shown.bmp;
+      /* That way it won't be overwritten. */
+      is->shown.bmp = is->video->current_frame;
+
+      is->video->position = get_master_clock(is);
+      is->video->video_position = get_video_clock(is);
+      is->video->audio_position = get_audio_clock(is);
+      
+      is->show_next = is->video->position + actual_delay;
+      al_signal_cond(is->timer_cond);
+
+      if (is->video_st->codec->sample_aspect_ratio.num == 0) {
+         is->video->aspect_ratio = 0;
+      }
+      else {
+         is->video->aspect_ratio = av_q2d(is->video_st->codec->sample_aspect_ratio) *
+             is->video_st->codec->width / is->video_st->codec->height;
+      }
+      if (is->video->aspect_ratio <= 0.0) {
+         is->video->aspect_ratio = (float)is->video_st->codec->width /
+             (float)is->video_st->codec->height;
+      }
+   }
+   else
+      is->dropped_count++;
+   
+   //printf("[%d] %f %s\n", is->pictq_rindex,
+   //   actual_delay, vp->dropped ? "dropped" : "shown");
+
+   /* update queue for next picture! */
+   if (++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
+      is->pictq_rindex = 0;
+   }
+   
+   al_lock_mutex(is->pictq_mutex);
+   is->pictq_size--;
+   al_signal_cond(is->pictq_cond);
+   al_unlock_mutex(is->pictq_mutex);
+
+   /* We skipped a frame... let's grab more until we catch up. */
+   if (actual_delay < 0)
+      video_refresh_timer(userdata);
+}
+
+static void alloc_picture(VideoState *is)
+{
+
+   VideoPicture *vp;
+
+   vp = &is->pictq[is->pictq_windex];
+   if (vp->bmp) {
+      // we already have one make another, bigger/smaller
+      al_destroy_bitmap(vp->bmp);
+   }
+
+   al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_BGR_888);
+   vp->bmp = al_create_bitmap(is->video_st->codec->width,
+                              is->video_st->codec->height);
+   vp->width = is->video_st->codec->width;
+   vp->height = is->video_st->codec->height;
+}
+
+/* Must be called from the user thread. */
+static void retrieve_picture(VideoState *is)
+{
+   VideoPicture *vp;
+   int dst_pix_fmt;
+   AVPicture pict;
+   static struct SwsContext *img_convert_ctx;
+   AVFrame *pFrame;
+   
+   if (!is->got_picture) return;
+
+   vp = &is->pictq[is->pictq_windex];
+   
+   pFrame = vp->frame;
+   
+   if (!vp->bmp ||
+       vp->width != is->video_st->codec->width ||
+       vp->height != is->video_st->codec->height) {
+       alloc_picture(is);
+   }
+   
+   /* YUV->RGB conversion. */
+   if (vp->bmp) {
+      /* Don't waste CPU on an outdated frame. */
+      if (get_video_clock(is) >= get_external_clock(is) - 0.25) {
+         ALLEGRO_LOCKED_REGION *lock;
+         lock = al_lock_bitmap(vp->bmp, 0, ALLEGRO_LOCK_WRITEONLY);
+
+         dst_pix_fmt = PIX_FMT_RGB24;
+
+         pict.data[0] = lock->data;
+         pict.linesize[0] = lock->pitch;
+
+         if (img_convert_ctx == NULL) {
+            int w = is->video_st->codec->width;
+            int h = is->video_st->codec->height;
+            img_convert_ctx = sws_getContext(w, h,
+                                             is->video_st->codec->pix_fmt, w, h,
+                                             dst_pix_fmt, SWS_BICUBIC, NULL, NULL,
+                                             NULL);
+            if (img_convert_ctx == NULL) {
+               ALLEGRO_ERROR("Cannot initialize the conversion context!\n");
+               return;
+            }
+         }
+         sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize,
+                   0, is->video_st->codec->height, pict.data, pict.linesize);
+
+         al_unlock_bitmap(vp->bmp);
+      }
+      else {
+         vp->dropped = true;
+      }
+      //printf("[%d] %f %f %f %s\n", is->pictq_windex,
+      //   vp->pts, get_video_clock(is), get_external_clock(is), vp->dropped ? "dropped" : "shown");
+
+      if (++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
+         is->pictq_windex = 0;
+      }
+   }
+
+   al_lock_mutex(is->pictq_mutex);
+   is->pictq_size++;
+   is->got_picture--;
+   vp->allocated = 1;
+   al_signal_cond(is->pictq_cond);
+   al_unlock_mutex(is->pictq_mutex);
+}
+
+static int queue_picture(VideoState * is, AVFrame * pFrame, double pts)
+{
+   VideoPicture *vp;
+   
+   if (!is->first) {
+      is->first = true;
+      is->external_clock_start = av_gettime();
+   }
+
+   /* wait until we have space for a new pic */
+   al_lock_mutex(is->pictq_mutex);
+   while (is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE && !is->quit) {
+      al_wait_cond(is->pictq_cond, is->pictq_mutex);
+   }
+   al_unlock_mutex(is->pictq_mutex);
+
+   if (is->quit)
+      return -1;
+
+   // windex is set to 0 initially
+   vp = &is->pictq[is->pictq_windex];
+   vp->dropped = false;
+
+   {
+      ALLEGRO_EVENT event;
+
+      vp->frame = pFrame;
+      vp->pts = pts;
+      vp->allocated = 0;
+      /* we have to do it in the main thread */
+      //printf("allocate %d (%4.1f ms)\n", is->pictq_windex,
+      //   get_master_clock(is) * 1000);
+      event.type = ALLEGRO_EVENT_VIDEO_FRAME_ALLOC;
+      event.user.data1 = (intptr_t)is->video;
+      al_emit_user_event(&is->video->es, &event, NULL);
+
+      /* wait until we have a picture allocated */
+      al_lock_mutex(is->pictq_mutex);
+      is->got_picture++;
+      while (!vp->allocated && !is->quit) {
+         al_wait_cond(is->pictq_cond, is->pictq_mutex);
+      }
+      al_unlock_mutex(is->pictq_mutex);
+      if (is->quit) {
+         return -1;
+      }
+   }
+
+   return 0;
+}
+
+// FIXME: Need synching! but right now it's all broken...
+#if 0
+static double synchronize_video(VideoState * is, AVFrame * src_frame,
+   double pts)
+{
+
+   double frame_delay;
+
+   if (pts != 0) {
+      /* if we have pts, set video clock to it */
+      is->video_clock = pts;
+   }
+   else {
+      /* if we aren't given a pts, set it to the clock */
+      pts = is->video_clock;
+   }
+   /* update the video clock */
+   frame_delay = av_q2d(is->video_st->codec->time_base);
+   /* if we are repeating a frame, adjust clock accordingly */
+   frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
+   is->video_clock += frame_delay;
+   return pts;
+}
+#endif
+
+/* These are called whenever we allocate a frame
+ * buffer. We use this to store the global_pts in
+ * a frame at the time it is allocated.
+ */
+static int our_get_buffer(struct AVCodecContext *c, AVFrame * pic)
+{
+   int ret = avcodec_default_get_buffer(c, pic);
+   uint64_t *pts = av_malloc(sizeof(uint64_t));
+   *pts = FIXME_global_video_pkt_pts;
+   pic->opaque = pts;
+   return ret;
+}
+
+static void our_release_buffer(struct AVCodecContext *c, AVFrame * pic)
+{
+   if (pic)
+      av_freep(&pic->opaque);
+   avcodec_default_release_buffer(c, pic);
+}
+
+static void *video_thread(ALLEGRO_THREAD * t, void *arg)
+{
+   VideoState *is = (VideoState *) arg;
+   AVPacket pkt1, *packet = &pkt1;
+   int len1, frameFinished;
+   AVFrame *pFrame;
+   double pts;
+   (void)t;
+
+   pFrame = avcodec_alloc_frame();
+
+   for (;;) {
+      if (packet_queue_get(is, &is->videoq, packet, 1) < 0) {
+         // means we quit getting packets
+         break;
+      }
+      if (packet->data == flush_pkt.data) {
+         avcodec_flush_buffers(is->video_st->codec);
+         continue;
+      }
+      pts = 0;
+
+      // Save global pts to be stored in pFrame
+      FIXME_global_video_pkt_pts = packet->pts;
+   
+      // Decode video frame
+      len1 = avcodec_decode_video(is->video_st->codec, pFrame, &frameFinished,
+                                  packet->data, packet->size);
+                                  
+
+      if (packet->dts == NOPTS_VALUE
+          && pFrame->opaque && *(int64_t *) pFrame->opaque != NOPTS_VALUE) {
+         pts = 0;//*(uint64_t *) pFrame->opaque;
+      }
+      else if (packet->dts != NOPTS_VALUE) {
+         pts = packet->dts;
+      }
+      else {
+         pts = 0;
+      }
+      pts *= av_q2d(is->video_st->time_base);
+
+      // Did we get a video frame?
+      if (frameFinished) {
+         //pts = synchronize_video(is, pFrame, pts);
+         if (queue_picture(is, pFrame, pts) < 0) {
+            break;
+         }
+      }
+      av_free_packet(packet);
+   }
+   av_free(pFrame);
+   return NULL;
+}
+
+static void *stream_audio(ALLEGRO_THREAD *thread, void *data)
+{
+   ALLEGRO_EVENT_QUEUE *queue;
+   VideoState *is = data;
+   ALLEGRO_AUDIO_STREAM *audio_stream = is->video->audio;
+
+   queue = al_create_event_queue();
+   
+   if (is->video->mixer)
+      al_attach_audio_stream_to_mixer(audio_stream, is->video->mixer);
+   else if (is->video->voice)
+      al_attach_audio_stream_to_voice(audio_stream, is->video->voice);
+   else
+      al_attach_audio_stream_to_mixer(audio_stream, al_get_default_mixer());
+
+   al_register_event_source(queue,
+      al_get_audio_stream_event_source(audio_stream));
+
+   while (1) {
+      ALLEGRO_EVENT event;
+      al_wait_for_event(queue, &event);
+
+      if (event.type == ALLEGRO_EVENT_AUDIO_STREAM_FRAGMENT) {
+         void *buf = al_get_audio_stream_fragment(audio_stream);
+         if (!buf)
+            continue;
+         audio_callback(is, buf, SDL_AUDIO_BUFFER_SIZE);
+         al_set_audio_stream_fragment(audio_stream, buf);
+      }
+   }
+
+   return thread;
+}
+
+static int stream_component_open(VideoState * is, int stream_index)
+{
+
+   AVFormatContext *format_context = is->format_context;
+   AVCodecContext *codecCtx;
+   AVCodec *codec;
+
+   if (stream_index < 0 || stream_index >= (int)format_context->nb_streams) {
+      return -1;
+   }
+
+   // Get a pointer to the codec context for the video stream
+   codecCtx = format_context->streams[stream_index]->codec;
+
+   if (codecCtx->codec_type == CODEC_TYPE_AUDIO) {
+      // Set audio settings from codec info
+      is->video->audio =
+          al_create_audio_stream(4, SDL_AUDIO_BUFFER_SIZE / 4,
+                                 codecCtx->sample_rate,
+                                 ALLEGRO_AUDIO_DEPTH_INT16,
+                                 ALLEGRO_CHANNEL_CONF_1 + codecCtx->channels -
+                                 1);
+
+      if (!is->video->audio) {
+         ALLEGRO_ERROR("al_create_audio_stream failed\n");
+         return -1;
+      }
+
+      is->audio_thread = al_create_thread(stream_audio, is);
+      al_start_thread(is->audio_thread);
+
+      is->audio_hw_buf_size = SDL_AUDIO_BUFFER_SIZE;
+   }
+   codec = avcodec_find_decoder(codecCtx->codec_id);
+   if (!codec || (avcodec_open(codecCtx, codec) < 0)) {
+      ALLEGRO_ERROR("Unsupported codec!\n");
+      return -1;
+   }
+
+   switch (codecCtx->codec_type) {
+      case CODEC_TYPE_AUDIO:
+         is->audioStream = stream_index;
+         is->audio_st = format_context->streams[stream_index];
+         is->audio_buf_size = 0;
+         is->audio_buf_index = 0;
+
+         /* averaging filter for audio sync */
+         is->audio_diff_avg_coef = exp(log(0.01 / AUDIO_DIFF_AVG_NB));
+         is->audio_diff_avg_count = 0;
+         /* Correct audio only if larger error than this */
+         is->audio_diff_threshold = 0.1;
+
+         memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
+         packet_queue_init(&is->audioq);
+
+         break;
+      case CODEC_TYPE_VIDEO:
+         is->videoStream = stream_index;
+         is->video_st = format_context->streams[stream_index];
+
+         is->frame_timer = (double)av_gettime() / 1000000.0;
+         is->frame_last_delay = 40e-3;
+         is->video_current_pts_time = av_gettime();
+
+         packet_queue_init(&is->videoq);
+         is->video_thread = al_create_thread(video_thread, is);
+         al_start_thread(is->video_thread);
+         codecCtx->get_buffer = our_get_buffer;
+         codecCtx->release_buffer = our_release_buffer;
+
+         break;
+      default:
+         break;
+   }
+
+   return 0;
+}
+
+static void *decode_thread(ALLEGRO_THREAD *t, void *arg)
+{
+   VideoState *is = (VideoState *) arg;
+   AVFormatContext *format_context = is->format_context;
+   AVPacket pkt1, *packet = &pkt1;
+
+   is->videoStream = -1;
+   is->audioStream = -1;
+
+   if (is->audio_index >= 0) {
+      stream_component_open(is, is->audio_index);
+   }
+   if (is->video_index >= 0) {
+      stream_component_open(is, is->video_index);
+   }
+
+   if (is->videoStream < 0 && is->audioStream < 0) {
+      ALLEGRO_ERROR("%s: could not open codecs\n", is->filename);
+      goto fail;
+   }
+
+   for (;;) {
+      if (is->quit) {
+         break;
+      }
+
+      if (is->seek_req) {
+         int stream_index = -1;
+         int64_t seek_target = is->seek_pos;
+
+         if (is->videoStream >= 0)
+            stream_index = is->videoStream;
+         else if (is->audioStream >= 0)
+            stream_index = is->audioStream;
+
+         if (stream_index >= 0) {
+            seek_target =
+                av_rescale_q(seek_target, AV_TIME_BASE_Q,
+                             format_context->streams[stream_index]->time_base);
+         }
+         
+         if (av_seek_frame(is->format_context, stream_index, seek_target,
+            is->seek_flags) < 0) {
+            ALLEGRO_WARN("%s: error while seeking (%d, %lu)\n",
+                    is->format_context->filename, stream_index, seek_target);
+         }
+         else {
+            if (is->audioStream >= 0) {
+               packet_queue_flush(&is->audioq);
+               packet_queue_put(&is->audioq, &flush_pkt);
+            }
+            if (is->videoStream >= 0) {
+               packet_queue_flush(&is->videoq);
+               packet_queue_put(&is->videoq, &flush_pkt);
+            }
+         }
+         is->seek_req = 0;
+         is->after_seek_sync = true;
+         
+      }
+      if (is->audioq.size > MAX_AUDIOQ_SIZE ||
+          is->videoq.size > MAX_VIDEOQ_SIZE) {
+         al_rest(0.01);
+         continue;
+      }
+      if (av_read_frame(is->format_context, packet) < 0) {
+         if (url_ferror((void *)&format_context->pb) == 0) {
+            al_rest(0.1);
+            continue;
+         }
+         else {
+            break;
+         }
+      }
+      // Is this a packet from the video stream?
+      if (packet->stream_index == is->videoStream) {
+         packet_queue_put(&is->videoq, packet);
+      }
+      else if (packet->stream_index == is->audioStream) {
+         packet_queue_put(&is->audioq, packet);
+      }
+      else {
+         av_free_packet(packet);
+      }
+   }
+   /* all done - wait for it */
+   while (!is->quit) {
+      al_rest(0.1);
+   }
+
+ fail:
+   return t;
+}
+
+/* We want to be able to send an event to the user exactly at the time
+ * a new video frame should be displayed.
+ */
+static void *timer_thread(ALLEGRO_THREAD *t, void *arg)
+{
+   VideoState *is = (VideoState *) arg;
+   double ot = 0, nt = 0;
+   while (!is->quit) {
+      ALLEGRO_EVENT event;
+      double d;
+
+      /* Wait here until someone signals to us when a new frame was
+       * scheduled at is->show_next.
+       */
+      al_lock_mutex(is->timer_mutex);
+      al_wait_cond(is->timer_cond, is->timer_mutex);
+      al_unlock_mutex(is->timer_mutex);
+      
+      if (is->quit) break;
+      
+      /* Wait until that time. This wait is why we have our own thread
+       * here so the user doesn't need to do it.
+       */
+      while (1) {
+         d = is->show_next - get_master_clock(is);
+         if (d <= 0) break;
+         //printf("waiting %4.1f ms\n", d * 1000);
+         al_rest(d);
+      }
+
+      nt = get_master_clock(is);
+      //printf("event after %4.1f ms\n", (nt - ot) * 1000);
+      ot = nt;
+      /* Now is the time. */
+      event.type = ALLEGRO_EVENT_VIDEO_FRAME_SHOW;
+      event.user.data1 = (intptr_t)is->video;
+      al_emit_user_event(&is->video->es, &event, NULL);
+   }
+   return t;
+}
+
+static void init(void)
+{
+   if (init_once) return;
+   init_once = true;
+   // Register all formats and codecs
+   av_register_all();
+   
+   av_init_packet(&flush_pkt);
+   flush_pkt.data = (void *)"FLUSH";
+}
+
+static bool open_video(ALLEGRO_VIDEO *video)
+{
+   VideoState *is = av_mallocz(sizeof *is);
+   int i;
+   AVRational fps;
+
+   is->video = video;
+   
+   init();
+   
+   video->data = is;
+   strncpy(is->filename, al_path_cstr(video->filename, '/'),
+      sizeof(is->filename));
+   
+   is->av_sync_type = DEFAULT_AV_SYNC_TYPE;
+
+   // Open video file
+   if (av_open_input_file(&is->format_context, is->filename, NULL, 0,
+      NULL) != 0) {
+      av_free(is);
+      return false;
+   }
+
+   dump_format(is->format_context, 0, is->filename, 0);
+   
+   if (av_find_stream_info(is->format_context) < 0) {
+      av_free(is);
+      return false;
+   }
+
+   is->video_index = -1;
+   is->audio_index = -1;
+   for (i = 0; i < (int)is->format_context->nb_streams; i++) {
+      if (is->format_context->streams[i]->codec->codec_type ==
+         CODEC_TYPE_VIDEO && is->video_index < 0) {
+         is->video_index = i;
+      }
+      if (is->format_context->streams[i]->codec->codec_type ==
+         CODEC_TYPE_AUDIO && is->audio_index < 0) {
+         is->audio_index = i;
+      }
+   }
+   
+   fps = is->format_context->streams[is->video_index]->r_frame_rate;
+   video->fps = (double)fps.num / fps.den;
+   video->audio_rate = is->format_context->streams[is->audio_index]->
+      codec->sample_rate;
+   video->width = is->format_context->streams[is->video_index]->codec->width;
+   video->height = is->format_context->streams[is->video_index]->codec->height;
+
+   is->pictq_mutex = al_create_mutex();
+   is->pictq_cond = al_create_cond();
+   
+   is->timer_mutex = al_create_mutex();
+   is->timer_cond = al_create_cond();
+
+   return true;
+}
+
+static bool close_video(ALLEGRO_VIDEO *video)
+{
+   VideoState *is = video->data;
+   
+   is->quit = true;
+   
+   if (is->timer_thread) {
+   
+      al_lock_mutex(is->timer_mutex);
+      al_signal_cond(is->timer_cond);
+      al_unlock_mutex(is->timer_mutex);
+   
+      al_join_thread(is->timer_thread, NULL);
+   }
+   
+   if (is->parse_thread) {
+      al_join_thread(is->parse_thread, NULL);
+   }
+
+   al_destroy_mutex(is->timer_mutex);
+   al_destroy_cond(is->timer_cond);
+
+   av_free(is);
+   return true;
+}
+
+static bool start_video(ALLEGRO_VIDEO *video)
+{
+   VideoState *is = video->data;
+
+   is->timer_thread = al_create_thread(timer_thread, is);
+   al_start_thread(is->timer_thread);
+
+   is->parse_thread = al_create_thread(decode_thread, is);
+   if (!is->parse_thread) {
+      return false;
+   }
+   al_start_thread(is->parse_thread);
+   return true;
+}
+
+static bool pause_video(ALLEGRO_VIDEO *video)
+{
+   VideoState *is = video->data;
+   is->paused = video->paused;
+   if (!is->paused) {
+      is->after_seek_sync = true;
+   }
+   return true;
+}
+
+static bool seek_video(ALLEGRO_VIDEO *video)
+{
+   VideoState *is = video->data;
+   if (!is->seek_req) {
+      is->seek_pos = video->seek_to * AV_TIME_BASE;
+      is->seek_flags = video->seek_to < video->position ? AVSEEK_FLAG_BACKWARD : 0;
+      is->seek_req = 1;
+      return true;
+   }
+   return false;
+}
+
+static bool update_video(ALLEGRO_VIDEO *video)
+{
+   VideoState *is = video->data;
+   retrieve_picture(is); 
+   video_refresh_timer(is);   
+   return true;
+}
+
+static ALLEGRO_VIDEO_INTERFACE ffmpeg_vtable = {
+   open_video,
+   close_video,
+   start_video,
+   pause_video,
+   seek_video,
+   update_video
+};
+
+ALLEGRO_VIDEO_INTERFACE *_al_video_vtable = &ffmpeg_vtable;
diff --git a/addons/video/video.c b/addons/video/video.c
new file mode 100644
index 0000000..04cddbd
--- /dev/null
+++ b/addons/video/video.c
@@ -0,0 +1,172 @@
+/* This is just a quick hack. But good enough for our use of displaying
+ * a short intro video when our game starts up - so might as well share
+ * it.
+ * 
+ * Known bugs:
+ * 
+ * - Only very crude synching. Audio is slightly delayed and some
+ *   videos seem to constantly drift off and then the audio gets all
+ *   distorted...
+ *
+ * - Seeking/Pausing doesn't really work.
+ * 
+ * - Memory leaks. Easy to fix but don't have time right now.
+ *
+ * Missing features:
+ * 
+ * - Stream information. For example allow selection of one of several
+ *   audio streams or subtitle overlay streams.
+ * 
+ * - Non audio/video streams. For example something like:
+ *   ALLEGRO_USTR *al_get_video_subtitle(float *x, float *y);
+ * 
+ * - Buffering. Right now buffering is hardcoded to a fixed size which
+ *   seemed enough for streaming 720p from disk in my tests. Obviously
+ *   when streaming higher bandwidth or from a source with high
+ *   fluctuation like an internet stream this won't work at all.
+ * 
+ * - Provide an audio stream for the audio. Then could use this to
+ *   stream audio files. Right now opening an .mp3 with the video
+ *   addon will play it but only with the video API instead of Allegro's
+ *   normal audio streaming API...
+ * 
+ * - Audio/Video sync. For a game user-controlled sync is probably not
+ *   too important as it can just ship with a properly synchronizeded
+ *   video. However right now the audio delay is completely ignored.
+ * 
+ * - Additional drivers. Also redo the API a bit so not everything
+ *   has to be done by the driver.
+ */
+ 
+#include "allegro5/allegro5.h"
+#include "allegro5/allegro_video.h"
+#include "allegro5/internal/aintern_video.h"
+
+/* Function: al_open_video
+ */
+ALLEGRO_VIDEO *al_open_video(char const *filename)
+{
+   ALLEGRO_VIDEO *video;
+
+   video = al_calloc(1, sizeof *video);
+   
+   video->vtable = _al_video_vtable;
+   
+   video->filename = al_create_path(filename);
+
+   if (!video->vtable->open_video(video)) {
+      al_destroy_path(video->filename);
+      al_free(video);
+      return NULL;
+   }
+   
+   al_init_user_event_source(&video->es);
+   
+   return video;
+}
+
+/* Function: al_close_video
+ */
+void al_close_video(ALLEGRO_VIDEO *video)
+{
+   video->vtable->close_video(video);
+}
+
+/* Function: al_get_video_event_source
+ */
+ALLEGRO_EVENT_SOURCE *al_get_video_event_source(ALLEGRO_VIDEO *video)
+{
+   return &video->es;
+}
+
+/* Function: al_start_video
+ */
+void al_start_video(ALLEGRO_VIDEO *video, ALLEGRO_MIXER *mixer)
+{
+   video->mixer = mixer;
+   video->vtable->start_video(video);
+}
+
+/* Function: al_start_video_with_voice
+ */
+void al_start_video_with_voice(ALLEGRO_VIDEO *video, ALLEGRO_VOICE *voice)
+{
+   video->voice = voice;
+   video->vtable->start_video(video);
+}
+
+/* Function: al_pause_video
+ */
+void al_pause_video(ALLEGRO_VIDEO *video, bool paused)
+{
+   if (paused == video->paused) return;
+   video->paused = paused;
+   video->vtable->pause_video(video);
+}
+
+/* Function: al_is_video_paused
+ */
+bool al_is_video_paused(ALLEGRO_VIDEO *video)
+{
+   return video->paused;
+}
+
+/* Function: al_get_video_frame
+ */
+ALLEGRO_BITMAP *al_get_video_frame(ALLEGRO_VIDEO *video)
+{
+   video->vtable->update_video(video);
+   return video->current_frame;
+}
+
+/* Function: al_get_video_position
+ */
+double al_get_video_position(ALLEGRO_VIDEO *video, int which)
+{
+   if (which == 1) return video->video_position;
+   if (which == 2) return video->audio_position;
+   return video->position;
+}
+
+/* Function: al_seek_video
+ */
+void al_seek_video(ALLEGRO_VIDEO *video, double pos_in_seconds)
+{
+   video->seek_to = pos_in_seconds;
+   video->vtable->seek_video(video);
+}
+
+/* Function: al_get_video_aspect_ratio
+ */
+double al_get_video_aspect_ratio(ALLEGRO_VIDEO *v)
+{
+   return v->aspect_ratio;
+}
+
+/* Function: al_get_video_audio_rate
+ */
+double al_get_video_audio_rate(ALLEGRO_VIDEO *v)
+{
+   return v->audio_rate;
+}
+
+/* Function: al_get_video_fps
+ */
+double al_get_video_fps(ALLEGRO_VIDEO *v)
+{
+   return v->fps;
+}
+
+/* Function: al_get_video_width
+ */
+int al_get_video_width(ALLEGRO_VIDEO *v)
+{
+   return v->width;
+}
+
+/* Function: al_get_video_height
+ */
+int al_get_video_height(ALLEGRO_VIDEO *v)
+{
+   return v->height;
+}
diff --git a/cmake/AllegroFindFFMPEG.cmake b/cmake/AllegroFindFFMPEG.cmake
new file mode 100644
index 0000000..d383b73
--- /dev/null
+++ b/cmake/AllegroFindFFMPEG.cmake
@@ -0,0 +1,13 @@
+# - Find FFMPEG
+#
+#  FFMPEG_FOUND       - true if FFMPEG is found
+#  FFMPEG_CFLAGS      - required compiler flags
+#  FFMPEG_LDFLAGS     - required linker flags
+
+# -lavcodec -lavformat -lswscale
+
+if(ALLEGRO_UNIX)
+   pkg_check_modules(FFMPEG libavcodec libavformat libswscale)
+endif()
+
+# TODO: Windos and OSX
diff --git a/docs/Refman.cmake b/docs/Refman.cmake
index 4c369f2..78099e6 100644
--- a/docs/Refman.cmake
+++ b/docs/Refman.cmake
@@ -41,6 +41,7 @@ set(PAGES
     physfs
     primitives
     shader
+    video
     )
 
 set(PAGES_TXT)
diff --git a/docs/src/refman/inc.a.txt b/docs/src/refman/inc.a.txt
index 71dbd6c..525fb82 100644
--- a/docs/src/refman/inc.a.txt
+++ b/docs/src/refman/inc.a.txt
@@ -40,6 +40,7 @@
 * [PhysicsFS addon](physfs.html)
 * [Primitives addon](primitives.html)
 * [Shader addon](shader.html)
+* [Video streaming addon](video.html)
 </div>
 
 <div class="searchbox">
diff --git a/docs/src/refman/video.txt b/docs/src/refman/video.txt
new file mode 100644
index 0000000..fb2f42b
--- /dev/null
+++ b/docs/src/refman/video.txt
@@ -0,0 +1,98 @@
+# Video streaming addon
+
+These functions are declared in the following header file.
+Link with allegro_video.
+
+    #include <allegro5/allegro_video.h>
+    
+Right now the only backend is ffmpeg. See http://ffmpeg.org/ for
+installation instructions, licensing information and supported video
+formats.
+
+## API: ALLEGRO_VIDEO_EVENT_TYPE
+
+* ALLEGRO_EVENT_VIDEO_FRAME_ALLOC
+* ALLEGRO_EVENT_VIDEO_FRAME_SHOW
+
+## API: al_open_video
+
+Reads a video file. This does not start streaming yet but reads the
+meta info so you can query e.g. the size or audio rate.
+
+## API: al_close_video
+
+Closes the video and frees all allocated resources. The video pointer
+is invalid after the function returns.
+
+## API: al_start_video
+
+Starts streaming the video from the beginning.
+
+## API: al_start_video_with_voice
+
+Like [al_start_video] but audio is routed to the provided voice.
+
+## API: al_get_video_event_source
+
+Get an event source for the video. The possible events are described
+under [ALLEGRO_VIDEO_EVENT_TYPE].
+
+## API: al_pause_video
+
+Paused or resumes playback.
+
+## API: al_is_video_paused
+
+Returns true if the video is currently paused.
+
+## API: al_get_video_aspect_ratio
+
+Returns the aspect ratio of the video. Videos often do not use square
+pixels so you should always check the aspect ratio before displaying
+video frames.
+
+## API: al_get_video_audio_rate
+
+Returns the audio rate of the video, in Hz.
+
+## API: al_get_video_fps
+
+Returns the speed of the video in frames per second. Often this will
+not be an integer value.
+
+## API: al_get_video_width
+
+Returns the number of pixel raw pixel columns in the video stream.
+Multiply this with the aspect ratio to get the true width.
+
+See also: [al_get_video_aspect_ratio]
+
+## API: al_get_video_height
+
+Returns the number of rows in the video. Typically this will be
+720 or 1080.
+
+## API: al_get_video_frame
+
+Returns the current video frame. The bitmap is owned by the video so
+do not attempt to free it. The bitmap will stay valid until the next
+call to al_get_video_frame.
+
+## API: al_get_video_position
+
+Returns the current position of the video stream in seconds since the
+beginning. The parameter has the following meaning:
+
+* 0: Return the actual position.
+* 1: Return the video decoding position. If this lags behind video
+     decoding is taking too long and the video can't be displayed
+     properly.
+* 2: Return the audio decoding position. Audio gets easily out of sync
+     for some reason - but this addon does not provide any means to
+     do much about it.
+
+## API: al_seek_video
+
+Seek to a different position in the video. Right now this does not
+work very well in the ffmpeg backend when seeking backwards and will
+often lose audio/video synchronization if doing so.
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index e0e2700..c88dc2c 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -11,6 +11,7 @@ include_directories(
     ../addons/primitives
     ../addons/shader
     ../addons/ttf
+    ../addons/video
     )
 
 if(SUPPORT_PHYSFS)
@@ -76,6 +77,7 @@ set(PHYSFS x${PHYSFS_LINK_WITH})
 set(PRIM x${PRIMITIVES_LINK_WITH})
 set(TTF x${TTF_LINK_WITH})
 set(SHADER x${SHADER_LINK_WITH})
+set(VIDEO x${VIDEO_LINK_WITH})
 
 set(NIHGUI nihgui.cpp ${FONT} ${PRIM})
 
@@ -154,6 +156,7 @@ example(ex_vsync ${FONT} ${IMAGE})
 example(ex_warp_mouse ${FONT} ${PRIM} ${IMAGE})
 example(ex_windows ${FONT} ${IMAGE})
 example(ex_winfull)
+example(ex_video ${AUDIO} ${IMAGE} ${FONT} ${PRIM} ${VIDEO})
 
 if(WANT_D3D AND D3DX9_FOUND)
     example(ex_d3d ex_d3d.cpp ${D3DX9_LIBRARY})
diff --git a/examples/ex_video.c b/examples/ex_video.c
new file mode 100644
index 0000000..4a03982
--- /dev/null
+++ b/examples/ex_video.c
@@ -0,0 +1,222 @@
+#include <allegro5/allegro.h>
+#include <allegro5/allegro_audio.h>
+#include <allegro5/allegro_image.h>
+#include <allegro5/allegro_video.h>
+#include <allegro5/allegro_font.h>
+#include <allegro5/allegro_primitives.h>
+
+#include <stdio.h>
+
+ALLEGRO_DISPLAY *screen;
+ALLEGRO_FONT *font;
+char const *filename;
+float zoom = 0;
+
+static void video_display(ALLEGRO_VIDEO *video)
+{
+   /* Videos often do not use square pixels - this returns the aspect
+    * ratio of the pixels.
+    */
+   float aspect_ratio = al_get_video_aspect_ratio(video);
+   /* Get the currently visible frame of the video, based on clock
+    * time.
+    */
+   ALLEGRO_BITMAP *frame = al_get_video_frame(video);
+   int w, h, x, y;
+   ALLEGRO_COLOR tc = al_map_rgba_f(0, 0, 0, 0.5);
+   ALLEGRO_COLOR bc = al_map_rgba_f(0.5, 0.5, 0.5, 0.5);
+
+   if (!frame) return;
+
+   if (zoom == 0) {
+      /* Always make the video fit into the window. */
+      h = al_get_display_height(screen);
+      w = (int)(h * aspect_ratio);
+      if(w > al_get_display_width(screen)) {
+         w = al_get_display_width(screen);
+         h = (int)(w / aspect_ratio);
+      }
+   }
+   else {
+      w = al_get_video_width(video);
+      h = al_get_video_height(video);
+   }
+   x = (al_get_display_width(screen) - w) / 2;
+   y = (al_get_display_height(screen) - h) / 2;
+
+   /* Display the frame. */
+   al_draw_scaled_bitmap(frame, 0, 0,
+                         al_get_bitmap_width(frame),
+                         al_get_bitmap_height(frame), x, y, w, h, 0);
+
+   /* Show some video information. */
+   al_draw_filled_rounded_rectangle(4, 4, al_get_display_width(screen) - 4,
+      16 + 14 * 3, 8, 8, bc);
+   double p = al_get_video_position(video, 0);
+   al_draw_textf(font, tc, 8, 8 , 0, "%s", filename);
+   al_draw_textf(font, tc, 8, 8 + 14, 0, "%3d:%02d (V: %+5.2f A: %+5.2f)",
+      (int)(p / 60),
+      ((int)p) % 60,
+      al_get_video_position(video, 1) - p,
+      al_get_video_position(video, 2) - p);
+   al_draw_textf(font, tc, 8, 8 + 14 * 2, 0,
+      "video rate %.02f (%dx%d, aspect %.1f) audio rate %.0f",
+         al_get_video_fps(video),
+         al_get_video_width(video),
+         al_get_video_height(video),
+         al_get_video_aspect_ratio(video),
+         al_get_video_audio_rate(video));
+   al_flip_display();
+   al_clear_to_color(al_map_rgb(0, 0, 0));
+}
+
+
+int main(int argc, char *argv[])
+{
+   ALLEGRO_EVENT_QUEUE *queue;
+   ALLEGRO_EVENT event;
+   ALLEGRO_TIMER *timer;
+   ALLEGRO_VIDEO *video;
+   bool fullscreen = false;
+
+   if (argc < 2) {
+      fprintf(stderr, "Usage: test <file>\n");
+      exit(1);
+   }
+   
+   al_init();
+
+   al_init_image_addon();
+   al_init_font_addon();
+   al_install_keyboard();
+
+   al_install_audio();
+   al_reserve_samples(0);
+   al_init_primitives_addon();
+   
+   /* In this example we use a fixed FPS timer. If the video is
+    * displayed in a game this probably makes most sense. In a
+    * dedicated video player you probably want to listen to
+    * ALLEGRO_EVENT_VIDEO_FRAME events and only redraw whenever one
+    * arrives - to reduce possible jitter and save CPU.
+    */
+   timer = al_create_timer(1.0 / 60);
+
+   al_set_new_display_flags(ALLEGRO_RESIZABLE);
+   screen = al_create_display(640, 480);
+   if (!screen) {
+      fprintf(stderr, "SDL: could not set video mode - exiting\n");
+      exit(1);
+   }
+   
+   font = al_load_font("data/fixed_font.tga", 0, 0);
+   if (!font) {
+      fprintf(stderr, "No font.\n");
+      exit(1);
+   }
+
+   filename = argv[1];
+   video = al_open_video(filename);
+   if (!video) {
+      printf("Cannot read %s.\n", filename);
+      exit(1);
+   }
+   printf("video FPS: %f\n", al_get_video_fps(video));
+   printf("video audio rate: %f\n", al_get_video_audio_rate(video));
+   printf(
+      "keys:\n"
+      "Space: Play/Pause\n"
+      "cursor right/left: seek 10 seconds\n"
+      "cursor up/down: seek one minute\n"
+      "F: toggle fullscreen\n"
+      "1: disable scaling\n"
+      "S: scale to window\n");
+
+   queue = al_create_event_queue();
+   al_register_event_source(queue, al_get_video_event_source(video));
+   al_register_event_source(queue, al_get_display_event_source(screen));
+   al_register_event_source(queue, al_get_timer_event_source(timer));
+   al_register_event_source(queue, al_get_keyboard_event_source());
+
+   al_start_video(video, al_get_default_mixer());
+   al_start_timer(timer);
+   for (;;) {
+      double incr;
+
+      al_wait_for_event(queue, &event);
+      switch (event.type) {
+         case ALLEGRO_EVENT_KEY_DOWN:
+            switch (event.keyboard.keycode) {
+               case ALLEGRO_KEY_SPACE:
+                  al_pause_video(video, !al_is_video_paused(video));
+                  break;
+               case ALLEGRO_KEY_ESCAPE:
+                  al_close_video(video);
+                  exit(0);
+                  break;
+               case ALLEGRO_KEY_LEFT:
+                  incr = -10.0;
+                  goto do_seek;
+               case ALLEGRO_KEY_RIGHT:
+                  incr = 10.0;
+                  goto do_seek;
+               case ALLEGRO_KEY_UP:
+                  incr = 60.0;
+                  goto do_seek;
+               case ALLEGRO_KEY_DOWN:
+                  incr = -60.0;
+                  goto do_seek;
+
+               do_seek:
+                  
+                     al_seek_video(video, al_get_video_position(video, 0) + incr);
+
+                  
+                  break;
+               case ALLEGRO_KEY_F:
+                  fullscreen = !fullscreen;
+                  al_toggle_display_flag(screen,
+                     ALLEGRO_FULLSCREEN_WINDOW, fullscreen);
+                  break;
+               
+               case ALLEGRO_KEY_1:
+                  zoom = 1;
+                  break;
+
+               case ALLEGRO_KEY_S:
+                  zoom = 0;
+                  break;
+               default:
+                  break;
+            }
+            break;
+         
+          case ALLEGRO_EVENT_DISPLAY_RESIZE:
+               al_acknowledge_resize(screen);
+               al_clear_to_color(al_map_rgb(0, 0, 0));
+               break;
+
+         case ALLEGRO_EVENT_TIMER:
+            /*
+            display_time += 1.0 / 60;
+            if (display_time >= video_time) {
+               video_time = display_time + video_refresh_timer(is);
+            }*/
+
+            video_display(video);
+            break;
+
+         case ALLEGRO_EVENT_DISPLAY_CLOSE:
+            al_close_video(video);
+            exit(0);
+            break;
+
+         /*case ALLEGRO_EVENT_VIDEO_FRAME:
+            al_get_video_frame((void *)event.user.data1);
+            break;*/
+         default:
+            break;
+      }
+   }
+   return 0;
+}
diff --git a/misc/allegro_video-4.9.pc.in b/misc/allegro_video-4.9.pc.in
deleted file mode 100644
index b054170..0000000
--- a/misc/allegro_video-4.9.pc.in
+++ /dev/null
@@ -1,14 +0,0 @@
-prefix=@prefix@
-exec_prefix=${prefix}
-libdir=@libdir@
-includedir=@includedir@
-version=@ALLEGRO_VERSION@
-suffix=@lib_type@@lib_linkage@
-
-Name: allegro_video
-Description: Allegro game programming library, video player addon
-Version: ${version}
-Libs: -L${libdir} -lallegro_video${suffix}
-Libs.private: @link_with@
-Requires: allegro_audio${suffix}-4.9 >= ${version}
-Cflags: -I${includedir}
diff --git a/misc/allegro_video.pc.in b/misc/allegro_video.pc.in
new file mode 100644
index 0000000..b054170
--- /dev/null
+++ b/misc/allegro_video.pc.in
@@ -0,0 +1,14 @@
+prefix=@prefix@
+exec_prefix=${prefix}
+libdir=@libdir@
+includedir=@includedir@
+version=@ALLEGRO_VERSION@
+suffix=@lib_type@@lib_linkage@
+
+Name: allegro_video
+Description: Allegro game programming library, video player addon
+Version: ${version}
+Libs: -L${libdir} -lallegro_video${suffix}
+Libs.private: @link_with@
+Requires: allegro_audio${suffix}-4.9 >= ${version}
+Cflags: -I${includedir}


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/