diff options
Diffstat (limited to 'src/libs/video/vidplayer.c')
-rw-r--r-- | src/libs/video/vidplayer.c | 481 |
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)); +} + |