summaryrefslogtreecommitdiff
path: root/src/libs/video/vidplayer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/video/vidplayer.c')
-rw-r--r--src/libs/video/vidplayer.c481
1 files changed, 481 insertions, 0 deletions
diff --git a/src/libs/video/vidplayer.c b/src/libs/video/vidplayer.c
new file mode 100644
index 0000000..09a506d
--- /dev/null
+++ b/src/libs/video/vidplayer.c
@@ -0,0 +1,481 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "vidplayer.h"
+
+#include "vidintrn.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/graphics/tfb_draw.h"
+#include "libs/log.h"
+#include "libs/memlib.h"
+#include "libs/sndlib.h"
+
+// video callbacks
+static void vp_BeginFrame (TFB_VideoDecoder*);
+static void vp_EndFrame (TFB_VideoDecoder*);
+static void* vp_GetCanvasLine (TFB_VideoDecoder*, uint32 line);
+static uint32 vp_GetTicks (TFB_VideoDecoder*);
+static bool vp_SetTimer (TFB_VideoDecoder*, uint32 msecs);
+
+
+static const TFB_VideoCallbacks vp_DecoderCBs =
+{
+ vp_BeginFrame,
+ vp_EndFrame,
+ vp_GetCanvasLine,
+ vp_GetTicks,
+ vp_SetTimer
+};
+
+// audio stream callbacks
+static bool vp_AudioStart (TFB_SoundSample* sample);
+static void vp_AudioEnd (TFB_SoundSample* sample);
+static void vp_BufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag);
+static void vp_QueueBuffer (TFB_SoundSample* sample, audio_Object buffer);
+
+static const TFB_SoundCallbacks vp_AudioCBs =
+{
+ vp_AudioStart,
+ NULL,
+ vp_AudioEnd,
+ vp_BufferTag,
+ vp_QueueBuffer
+};
+
+
+bool
+TFB_InitVideoPlayer (void)
+{
+ // now just a stub
+ return true;
+}
+
+void
+TFB_UninitVideoPlayer (void)
+{
+ // now just a stub
+}
+
+static inline sint32
+msecToTimeCount (sint32 msec)
+{
+ return msec * ONE_SECOND / 1000;
+}
+
+// audio-synced video playback frame function
+// the frame rate and timing is dictated by the audio
+static bool
+processAudioSyncedFrame (VIDEO_REF vid)
+{
+#define MAX_FRAME_LAG 8
+#define LAG_FRACTION 6
+#define SYNC_BIAS 1 / 3
+ int ret;
+ uint32 want_frame;
+ uint32 prev_want_frame;
+ sint32 wait_msec;
+ CONTEXT oldContext;
+ TimeCount Now = GetTimeCounter ();
+
+ if (!vid->playing)
+ return false;
+
+ if (Now < vid->frame_time)
+ return true; // not time yet
+
+ LockMutex (vid->guard);
+ want_frame = vid->want_frame;
+ UnlockMutex (vid->guard);
+
+ if (want_frame >= vid->decoder->frame_count)
+ {
+ vid->playing = false;
+ return false;
+ }
+
+ // this works like so (audio-synced):
+ // 1. you call VideoDecoder_Seek() [when necessary] and
+ // VideoDecoder_Decode()
+ // 2. wait till it's time for this frame to be drawn
+ // the timeout is necessary because the audio signaling is not
+ // precise (see vp_AudioStart, vp_AudioEnd, vp_BufferTag)
+ // 3. output the frame; if the audio is behind, the lag counter
+ // goes up; if the video is behind, the lag counter goes down
+ // 4. set the next frame timeout; lag counter increases or
+ // decreases the timeout to allow audio or video to catch up
+ // 5. on a seek operation, the audio stream is moved to the
+ // correct position and then the audio signals the frame
+ // that should be rendered
+ // The system of timeouts and lag counts should make the video
+ // *relatively* smooth
+ //
+ prev_want_frame = vid->cur_frame - vid->lag_cnt;
+ if (want_frame > prev_want_frame - MAX_FRAME_LAG
+ && want_frame <= prev_want_frame + MAX_FRAME_LAG)
+ {
+ // we will draw the next frame right now, thus +1
+ vid->lag_cnt = vid->cur_frame + 1 - want_frame;
+ }
+ else
+ { // out of sequence frame, let's get it
+ vid->lag_cnt = 0;
+ vid->cur_frame = VideoDecoder_SeekFrame (vid->decoder, want_frame);
+ ret = VideoDecoder_Decode (vid->decoder);
+ if (ret < 0)
+ { // decoder returned a failure
+ vid->playing = false;
+ return false;
+ }
+ }
+ vid->cur_frame = vid->decoder->cur_frame;
+
+ // draw the frame
+ // We have the cliprect precalculated and don't need the rest
+ oldContext = SetContext (NULL);
+ TFB_DrawScreen_Image (vid->frame,
+ vid->dst_rect.corner.x, vid->dst_rect.corner.y, 0, 0,
+ NULL, DRAW_REPLACE_MODE, TFB_SCREEN_MAIN);
+ SetContext (oldContext);
+ FlushGraphics (); // needed to prevent half-frame updates
+
+ // increase interframe with positive lag-count to allow audio to catch up
+ // decrease interframe with negative lag-count to allow video to catch up
+ wait_msec = vid->decoder->interframe_wait
+ - (int)vid->decoder->interframe_wait * SYNC_BIAS
+ + (int)vid->decoder->interframe_wait * vid->lag_cnt / LAG_FRACTION;
+ vid->frame_time = Now + msecToTimeCount (wait_msec);
+
+ ret = VideoDecoder_Decode (vid->decoder);
+ if (ret < 0)
+ {
+ // TODO: decide what to do on error
+ }
+
+ return vid->playing;
+}
+
+// audio-independent video playback frame function
+// the frame rate and timing is dictated by the video decoder
+static bool
+processMuteFrame (VIDEO_REF vid)
+{
+ int ret;
+ TimeCount Now = GetTimeCounter ();
+
+ if (!vid->playing)
+ return false;
+
+ // this works like so:
+ // 1. you call VideoDecoder_Seek() [when necessary] and
+ // VideoDecoder_Decode()
+ // 2. the decoder calls back vp_GetTicks() and vp_SetTimer()
+ // to figure out and tell you when to render the frame
+ // being decoded
+ // On a seek operation, the decoder should reset its internal
+ // clock and call vp_GetTicks() again
+ //
+ if (Now >= vid->frame_time)
+ {
+ CONTEXT oldContext;
+
+ vid->cur_frame = vid->decoder->cur_frame;
+
+ // We have the cliprect precalculated and don't need the rest
+ oldContext = SetContext (NULL);
+ TFB_DrawScreen_Image (vid->frame,
+ vid->dst_rect.corner.x, vid->dst_rect.corner.y, 0, 0,
+ NULL, DRAW_REPLACE_MODE, TFB_SCREEN_MAIN);
+ SetContext (oldContext);
+ FlushGraphics (); // needed to prevent half-frame updates
+
+ if (vid->cur_frame == vid->loop_frame)
+ VideoDecoder_SeekFrame (vid->decoder, vid->loop_to);
+
+ ret = VideoDecoder_Decode (vid->decoder);
+ if (ret <= 0)
+ vid->playing = false;
+ }
+
+ return vid->playing;
+}
+
+bool
+TFB_PlayVideo (VIDEO_REF vid, uint32 x, uint32 y)
+{
+ RECT scrn_r;
+ RECT clip_r = {{0, 0}, {vid->w, vid->h}};
+ RECT vid_r = {{0, 0}, {ScreenWidth, ScreenHeight}};
+ RECT dr = {{x, y}, {vid->w, vid->h}};
+ RECT sr;
+ bool loop_music = false;
+ int ret;
+
+ if (!vid)
+ return false;
+
+ // calculate the frame-source and screen-destination rects
+ GetContextClipRect (&scrn_r);
+ if (!BoxIntersect(&scrn_r, &vid_r, &scrn_r))
+ return false; // drawing outside visible
+
+ sr = dr;
+ sr.corner.x = -sr.corner.x;
+ sr.corner.y = -sr.corner.y;
+ if (!BoxIntersect (&clip_r, &sr, &sr))
+ return false; // drawing outside visible
+
+ dr.corner.x += scrn_r.corner.x;
+ dr.corner.y += scrn_r.corner.y;
+ if (!BoxIntersect (&scrn_r, &dr, &vid->dst_rect))
+ return false; // drawing outside visible
+
+ vid->src_rect = vid->dst_rect;
+ vid->src_rect.corner.x = sr.corner.x;
+ vid->src_rect.corner.y = sr.corner.y;
+
+ vid->decoder->callbacks = vp_DecoderCBs;
+ vid->decoder->data = vid;
+
+ vid->frame = TFB_DrawImage_CreateForScreen (vid->w, vid->h, FALSE);
+ vid->cur_frame = -1;
+ vid->want_frame = -1;
+
+ if (!vid->hAudio)
+ {
+ vid->hAudio = LoadMusicFile (vid->decoder->filename);
+ vid->own_audio = true;
+ }
+
+ if (vid->decoder->audio_synced)
+ {
+ if (!vid->hAudio)
+ {
+ log_add (log_Warning, "TFB_PlayVideo: "
+ "Cannot load sound-track for audio-synced video");
+ return false;
+ }
+
+ TFB_SetSoundSampleCallbacks (*vid->hAudio, &vp_AudioCBs);
+ TFB_SetSoundSampleData (*vid->hAudio, vid);
+ }
+
+ // get the first frame
+ ret = VideoDecoder_Decode (vid->decoder);
+ if (ret < 0)
+ return false;
+
+ vid->playing = true;
+
+ loop_music = !vid->decoder->audio_synced && vid->loop_frame != VID_NO_LOOP;
+ if (vid->hAudio)
+ PLRPlaySong (vid->hAudio, loop_music, 1);
+
+ if (vid->decoder->audio_synced)
+ {
+ // draw the first frame now
+ vid->frame_time = GetTimeCounter ();
+ }
+
+ return true;
+}
+
+void
+TFB_StopVideo (VIDEO_REF vid)
+{
+ if (!vid)
+ return;
+
+ vid->playing = false;
+
+ if (vid->hAudio)
+ {
+ PLRStop (vid->hAudio);
+ if (vid->own_audio)
+ {
+ DestroyMusic (vid->hAudio);
+ vid->hAudio = 0;
+ vid->own_audio = false;
+ }
+ }
+ if (vid->frame)
+ {
+ TFB_DrawScreen_DeleteImage (vid->frame);
+ vid->frame = NULL;
+ }
+}
+
+bool
+TFB_VideoPlaying (VIDEO_REF vid)
+{
+ if (!vid)
+ return false;
+
+ return vid->playing;
+}
+
+bool
+TFB_ProcessVideoFrame (VIDEO_REF vid)
+{
+ if (!vid)
+ return false;
+
+ if (vid->decoder->audio_synced)
+ return processAudioSyncedFrame (vid);
+ else
+ return processMuteFrame (vid);
+}
+
+uint32
+TFB_GetVideoPosition (VIDEO_REF vid)
+{
+ uint32 pos;
+
+ if (!TFB_VideoPlaying (vid))
+ return 0;
+
+ LockMutex (vid->guard);
+ pos = (uint32) (vid->decoder->pos * 1000);
+ UnlockMutex (vid->guard);
+
+ return pos;
+}
+
+bool
+TFB_SeekVideo (VIDEO_REF vid, uint32 pos)
+{
+ if (!TFB_VideoPlaying (vid))
+ return false;
+
+ if (vid->decoder->audio_synced)
+ {
+ PLRSeek (vid->hAudio, pos);
+ TaskSwitch ();
+ return true;
+ }
+ else
+ { // TODO: Non-a/s decoder seeking is not supported yet
+ // Decide what to do with these. Seeking this kind of
+ // video is trivial, but we may not want to do it.
+ // The only non-a/s videos right now are ship spins.
+ return false;
+ }
+}
+
+static void
+vp_BeginFrame (TFB_VideoDecoder* decoder)
+{
+ TFB_VideoClip* vid = decoder->data;
+
+ if (vid)
+ TFB_DrawCanvas_Lock (vid->frame->NormalImg);
+}
+
+static void
+vp_EndFrame (TFB_VideoDecoder* decoder)
+{
+ TFB_VideoClip* vid = decoder->data;
+
+ if (vid)
+ TFB_DrawCanvas_Unlock (vid->frame->NormalImg);
+}
+
+static void*
+vp_GetCanvasLine (TFB_VideoDecoder* decoder, uint32 line)
+{
+ TFB_VideoClip* vid = decoder->data;
+
+ if (!vid)
+ return NULL;
+
+ return TFB_DrawCanvas_GetLine (vid->frame->NormalImg, line);
+}
+
+static uint32
+vp_GetTicks (TFB_VideoDecoder* decoder)
+{
+ uint32 ctr = GetTimeCounter ();
+ return (ctr / ONE_SECOND) * 1000 + ((ctr % ONE_SECOND) * 1000) / ONE_SECOND;
+
+ (void)decoder; // gobble up compiler warning
+}
+
+static bool
+vp_SetTimer (TFB_VideoDecoder* decoder, uint32 msecs)
+{
+ TFB_VideoClip* vid = decoder->data;
+
+ if (!vid)
+ return false;
+
+ // time when next frame should be displayed
+ vid->frame_time = GetTimeCounter () + msecs * ONE_SECOND / 1000;
+ return true;
+}
+
+static bool
+vp_AudioStart (TFB_SoundSample* sample)
+{
+ TFB_VideoClip* vid = TFB_GetSoundSampleData (sample);
+ TFB_SoundDecoder *decoder;
+
+ assert (sizeof (intptr_t) >= sizeof (vid));
+ assert (vid != NULL);
+
+ decoder = TFB_GetSoundSampleDecoder (sample);
+
+ LockMutex (vid->guard);
+ vid->want_frame = SoundDecoder_GetFrame (decoder);
+ UnlockMutex (vid->guard);
+
+ return true;
+}
+
+static void
+vp_AudioEnd (TFB_SoundSample* sample)
+{
+ TFB_VideoClip* vid = TFB_GetSoundSampleData (sample);
+
+ assert (vid != NULL);
+
+ LockMutex (vid->guard);
+ vid->want_frame = vid->decoder->frame_count; // end it
+ UnlockMutex (vid->guard);
+}
+
+static void
+vp_BufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag)
+{
+ TFB_VideoClip* vid = TFB_GetSoundSampleData (sample);
+ uint32 frame = (uint32) tag->data;
+
+ assert (sizeof (tag->data) >= sizeof (frame));
+ assert (vid != NULL);
+
+ LockMutex (vid->guard);
+ vid->want_frame = frame; // let it go!
+ UnlockMutex (vid->guard);
+}
+
+static void
+vp_QueueBuffer (TFB_SoundSample* sample, audio_Object buffer)
+{
+ //TFB_VideoClip* vid = (TFB_VideoClip*) TFB_GetSoundSampleData (sample);
+ TFB_SoundDecoder *decoder = TFB_GetSoundSampleDecoder (sample);
+
+ TFB_TagBuffer (sample, buffer,
+ (intptr_t) SoundDecoder_GetFrame (decoder));
+}
+