diff --git a/sc2/src/libs/video/dukvid.c b/sc2/src/libs/video/dukvid.c index 48db6cd78..7d727d0c8 100644 --- a/sc2/src/libs/video/dukvid.c +++ b/sc2/src/libs/video/dukvid.c @@ -615,8 +615,7 @@ dukv_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) This->length = (float) dukv->cframes / DUCK_GENERAL_FPS; This->frame_count = dukv->cframes; - This->max_frame_wait = - (uint32) (1000.0f / DUCK_GENERAL_FPS * 1.1); + This->interframe_wait = (uint32) (1000.0 / DUCK_GENERAL_FPS); dukv->inbuf = HMalloc (DUCK_MAX_FRAME_SIZE); dukv->decbuf = HMalloc ( diff --git a/sc2/src/libs/video/legacyplayer.c b/sc2/src/libs/video/legacyplayer.c index 2e4986ef5..5dfddad10 100644 --- a/sc2/src/libs/video/legacyplayer.c +++ b/sc2/src/libs/video/legacyplayer.c @@ -61,8 +61,7 @@ StopLegacyVideo (LEGACY_VIDEO_REF ref) { if (!ref) return; - if (TFB_VideoPlaying (ref->vidref)) - VidStop (); + VidStop (); DestroyVideo (ref->vidref); if (ref->speechref) diff --git a/sc2/src/libs/video/video.c b/sc2/src/libs/video/video.c index a6f860275..8080592ae 100644 --- a/sc2/src/libs/video/video.c +++ b/sc2/src/libs/video/video.c @@ -74,6 +74,14 @@ VidPlaying (void) return NULL_VIDEO_REF; } +BOOLEAN +VidProcessFrame (void) +{ + if (!_cur_video) + return FALSE; + return TFB_ProcessVideoFrame (_cur_video); +} + // return current video position in milliseconds DWORD VidGetPosition (void) @@ -173,6 +181,9 @@ DestroyVideo (VIDEO_REF vid) if (!vid) return FALSE; + // just some armouring; should already be stopped + TFB_StopVideo (vid); + VideoDecoder_Free (vid->decoder); DestroyMutex (vid->guard); HFree (vid); diff --git a/sc2/src/libs/video/video.h b/sc2/src/libs/video/video.h index bd652d697..928c120ac 100644 --- a/sc2/src/libs/video/video.h +++ b/sc2/src/libs/video/video.h @@ -23,7 +23,6 @@ #include "types.h" #include "videodec.h" #include "libs/sound/sound.h" -#include "libs/tasklib.h" typedef struct tfb_videoclip @@ -36,11 +35,9 @@ typedef struct tfb_videoclip RECT dst_rect; // destination screen rect RECT src_rect; // source rect MUSIC_REF hAudio; - Task play_task; uint32 frame_time; // time when next frame should be rendered TFB_Image* frame; // frame preped and optimized for rendering uint32 cur_frame; // index of frame currently displayed - uint32 max_frame_wait; bool playing; bool own_audio; uint32 loop_frame; // frame index to loop from @@ -48,6 +45,7 @@ typedef struct tfb_videoclip Mutex guard; uint32 want_frame; // audio-signaled desired frame index + int lag_cnt; // N of frames video is behind or ahead of audio void* data; // user-defined data diff --git a/sc2/src/libs/video/videodec.h b/sc2/src/libs/video/videodec.h index 9f2c741ac..a645f06e7 100644 --- a/sc2/src/libs/video/videodec.h +++ b/sc2/src/libs/video/videodec.h @@ -74,7 +74,7 @@ struct tfb_videodecoder uint32 w, h; float length; // total length in seconds uint32 frame_count; - uint32 max_frame_wait; // maximum interframe delay in msecs + uint32 interframe_wait; // nominal interframe delay in msecs bool audio_synced; // decoder callbacks TFB_VideoCallbacks callbacks; diff --git a/sc2/src/libs/video/vidplayer.c b/sc2/src/libs/video/vidplayer.c index ade2820dc..cf9773228 100644 --- a/sc2/src/libs/video/vidplayer.c +++ b/sc2/src/libs/video/vidplayer.c @@ -55,163 +55,130 @@ static const TFB_SoundCallbacks vp_AudioCBs = vp_QueueBuffer }; -// inter-thread param guarded by mutex -static Semaphore vp_interthread_lock = 0; -static void* vp_interthread_clip = NULL; bool TFB_InitVideoPlayer (void) { - // creation probably needs better handling - vp_interthread_lock = CreateSemaphore (1, "inter-thread param lock", - SYNC_CLASS_VIDEO); - return vp_interthread_lock != 0; + // now just a stub + return true; } void TFB_UninitVideoPlayer (void) { - DestroySemaphore (vp_interthread_lock); + // now just a stub } -// audio-synced video playback task -// the frame rate and timing is dictated by the audio -static int -as_video_play_task (void *data) +static inline sint32 +msecToTimeCount (sint32 msec) { - Task task = (Task) data; - volatile TFB_VideoClip* vid; + 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; - TimeCount TimeOut; - int bTerm; - uint32 clagged; + uint32 prev_want_frame; + sint32 wait_msec; + CONTEXT oldContext; + TimeCount Now = GetTimeCounter (); - // snatch the clip pointer and release - vid = vp_interthread_clip; - vp_interthread_clip = NULL; - ClearSemaphore (vp_interthread_lock); + if (!vid->playing) + return false; + + if (Now < vid->frame_time) + return true; // not time yet LockMutex (vid->guard); want_frame = vid->want_frame; - if (vid->hAudio) - PLRPlaySong (vid->hAudio, FALSE, 1); UnlockMutex (vid->guard); - clagged = 0; + 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 the audio signals it's time for this frame - // or the maximum inter-frame timeout elapses; the timeout - // is necessary because the audio signaling is not precise - // (see vp_AudioStart, vp_AudioEnd, vp_BufferTag) - // 3. output the frame; if the timeout elapsed, increment the - // the lag counter - // 4. set the next frame timeout; lag counter increases the - // timeout to allow audio to catch up + // 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 // - ret = VideoDecoder_Decode (vid->decoder); - TimeOut = GetTimeCounter () + vid->decoder->max_frame_wait * - ONE_SECOND / 1000; - - while (!(bTerm = Task_ReadState (task, TASK_EXIT)) && ret > 0) + 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) { - // wait till its time to render next frame - while (!(bTerm = Task_ReadState (task, TASK_EXIT)) - && want_frame == vid->cur_frame - clagged - && TimeOut > GetTimeCounter ()) - { - TaskSwitch (); - LockMutex (vid->guard); - want_frame = vid->want_frame; - UnlockMutex (vid->guard); - } - - if (bTerm) - break; - - if (want_frame == vid->cur_frame - clagged) - { // timed out - draw the next frame - ++clagged; - } - else if (want_frame > vid->cur_frame - clagged - && want_frame <= vid->cur_frame) - { - // catching up - clagged = vid->cur_frame - want_frame; - // try again - continue; - } - else if (want_frame != vid->cur_frame + 1) - { // out of sequence frame, let's get it - vid->cur_frame = VideoDecoder_SeekFrame ( - vid->decoder, want_frame); - ret = VideoDecoder_Decode (vid->decoder); - clagged = 0; - } - else - { - clagged = 0; - } - vid->cur_frame = vid->decoder->cur_frame; - - // draw the frame - if (ret > 0) - { - CONTEXT oldContext; - - LockMutex (GraphicsLock); - // 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, - GSCALE_IDENTITY, NULL, TFB_SCREEN_MAIN); - SetContext (oldContext); - UnlockMutex (GraphicsLock); - FlushGraphics (); // needed to prevent half-frame updates - } - - // increase timeout with lag-count to allow audio to catch up - TimeOut = GetTimeCounter () + vid->decoder->max_frame_wait * - ONE_SECOND / 1000 + clagged * ONE_SECOND / 100; - - ret = VideoDecoder_Decode (vid->decoder); + // we will draw the next frame right now, thus +1 + vid->lag_cnt = vid->cur_frame + 1 - want_frame; } - if (vid->hAudio) - PLRStop (vid->hAudio); - vid->playing = false; + 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; - FinishTask (task); + // draw the frame + LockMutex (GraphicsLock); + // 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, + GSCALE_IDENTITY, NULL, TFB_SCREEN_MAIN); + SetContext (oldContext); + UnlockMutex (GraphicsLock); + FlushGraphics (); // needed to prevent half-frame updates - return 0; + // 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 task +// audio-independent video playback frame function // the frame rate and timing is dictated by the video decoder -static int -video_play_task (void *data) +static bool +processMuteFrame (VIDEO_REF vid) { - Task task = (Task) data; - TFB_VideoClip* vid; int ret; + TimeCount Now = GetTimeCounter (); - // snatch the clip pointer and release - vid = vp_interthread_clip; - vp_interthread_clip = NULL; - ClearSemaphore (vp_interthread_lock); - - LockMutex (vid->guard); - if (vid->hAudio) - PLRPlaySong (vid->hAudio, (vid->loop_frame != VID_NO_LOOP), 1); - - UnlockMutex (vid->guard); + if (!vid->playing) + return false; // this works like so: // 1. you call VideoDecoder_Seek() [when necessary] and @@ -222,14 +189,10 @@ video_play_task (void *data) // On a seek operation, the decoder should reset its internal // clock and call vp_GetTicks() again // - ret = VideoDecoder_Decode (vid->decoder); - - while (!Task_ReadState (task, TASK_EXIT) && ret > 0) + if (Now >= vid->frame_time) { CONTEXT oldContext; - // wait till its time to render next frame - SleepThreadUntil (vid->frame_time); vid->cur_frame = vid->decoder->cur_frame; LockMutex (GraphicsLock); @@ -246,14 +209,11 @@ video_play_task (void *data) VideoDecoder_SeekFrame (vid->decoder, vid->loop_to); ret = VideoDecoder_Decode (vid->decoder); + if (ret <= 0) + vid->playing = false; } - vid->playing = false; - if (vid->hAudio) - PLRStop (vid->hAudio); - FinishTask (task); - - return 0; + return vid->playing; } bool @@ -264,6 +224,8 @@ TFB_PlayVideo (VIDEO_REF vid, uint32 x, uint32 y) 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; @@ -318,24 +280,21 @@ TFB_PlayVideo (VIDEO_REF vid, uint32 x, uint32 y) TFB_SetSoundSampleData (*vid->hAudio, (intptr_t)vid); } - SetSemaphore (vp_interthread_lock); - vp_interthread_clip = 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) - vid->play_task = AssignTask ( - as_video_play_task, 4096, "a/s video player"); - else - vid->play_task = AssignTask ( - video_play_task, 4096, "video player"); - - if (!vid->play_task) { - vid->playing = false; - ClearSemaphore (vp_interthread_lock); - TFB_StopVideo (vid); - - return false; + // draw the first frame now + vid->frame_time = GetTimeCounter (); } return true; @@ -348,8 +307,6 @@ TFB_StopVideo (VIDEO_REF vid) return; vid->playing = false; - if (vid->play_task) - ConcludeTask (vid->play_task); if (vid->hAudio) { @@ -377,6 +334,18 @@ TFB_VideoPlaying (VIDEO_REF vid) 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) { diff --git a/sc2/src/libs/video/vidplayer.h b/sc2/src/libs/video/vidplayer.h index 6e9846fe9..354e38ec2 100644 --- a/sc2/src/libs/video/vidplayer.h +++ b/sc2/src/libs/video/vidplayer.h @@ -24,6 +24,7 @@ extern void TFB_UninitVideoPlayer (void); extern bool TFB_PlayVideo (VIDEO_REF VidRef, uint32 x, uint32 y); extern void TFB_StopVideo (VIDEO_REF VidRef); extern bool TFB_VideoPlaying (VIDEO_REF VidRef); +extern bool TFB_ProcessVideoFrame (VIDEO_REF vid); extern uint32 TFB_GetVideoPosition (VIDEO_REF VidRef); extern bool TFB_SeekVideo (VIDEO_REF VidRef, uint32 pos); diff --git a/sc2/src/libs/vidlib.h b/sc2/src/libs/vidlib.h index bdab2765e..786470814 100644 --- a/sc2/src/libs/vidlib.h +++ b/sc2/src/libs/vidlib.h @@ -47,6 +47,7 @@ extern VIDEO_TYPE VidPlayEx (VIDEO_REF VidRef, MUSIC_REF AudRef, #define VID_NO_LOOP (0U-1) extern void VidStop (void); extern VIDEO_REF VidPlaying (void); +extern BOOLEAN VidProcessFrame (void); extern DWORD VidGetPosition (void); // position in milliseconds extern BOOLEAN VidSeek (DWORD pos); // position in milliseconds diff --git a/sc2/src/uqm/intro.c b/sc2/src/uqm/intro.c index 118fadb3c..99a0ed67d 100644 --- a/sc2/src/uqm/intro.c +++ b/sc2/src/uqm/intro.c @@ -822,8 +822,6 @@ DoVideoInput (void *pIS) if (!PlayingLegacyVideo (pVIS->CurVideo)) { // Video probably finished - // Have to call VidStop anyway to cleanup - VidStop (); return FALSE; } @@ -832,7 +830,6 @@ DoVideoInput (void *pIS) || PulsedInputState.menu[KEY_MENU_SPECIAL] || (GLOBAL (CurrentActivity) & CHECK_ABORT)) { // abort movie - VidStop (); return FALSE; } else if (PulsedInputState.menu[KEY_MENU_LEFT] @@ -850,7 +847,10 @@ DoVideoInput (void *pIS) } else { - SleepThread (ONE_SECOND / 30); + if (!VidProcessFrame ()) + return FALSE; + + SleepThread (ONE_SECOND / 40); } return TRUE;