Video player task retired; minor legacy video semantical changes; improved video playback jitter compensation

git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@3297 8092fc87-c524-0410-9efc-e669fe64eaf9
This commit is contained in:
avolkov
2009-11-11 08:03:22 +00:00
parent cf28c52a8f
commit 393030ff6c
9 changed files with 134 additions and 156 deletions
+1 -2
View File
@@ -615,8 +615,7 @@ dukv_Open (THIS_PTR, uio_DirHandle *dir, const char *filename)
This->length = (float) dukv->cframes / DUCK_GENERAL_FPS; This->length = (float) dukv->cframes / DUCK_GENERAL_FPS;
This->frame_count = dukv->cframes; This->frame_count = dukv->cframes;
This->max_frame_wait = This->interframe_wait = (uint32) (1000.0 / DUCK_GENERAL_FPS);
(uint32) (1000.0f / DUCK_GENERAL_FPS * 1.1);
dukv->inbuf = HMalloc (DUCK_MAX_FRAME_SIZE); dukv->inbuf = HMalloc (DUCK_MAX_FRAME_SIZE);
dukv->decbuf = HMalloc ( dukv->decbuf = HMalloc (
+1 -2
View File
@@ -61,8 +61,7 @@ StopLegacyVideo (LEGACY_VIDEO_REF ref)
{ {
if (!ref) if (!ref)
return; return;
if (TFB_VideoPlaying (ref->vidref)) VidStop ();
VidStop ();
DestroyVideo (ref->vidref); DestroyVideo (ref->vidref);
if (ref->speechref) if (ref->speechref)
+11
View File
@@ -74,6 +74,14 @@ VidPlaying (void)
return NULL_VIDEO_REF; return NULL_VIDEO_REF;
} }
BOOLEAN
VidProcessFrame (void)
{
if (!_cur_video)
return FALSE;
return TFB_ProcessVideoFrame (_cur_video);
}
// return current video position in milliseconds // return current video position in milliseconds
DWORD DWORD
VidGetPosition (void) VidGetPosition (void)
@@ -173,6 +181,9 @@ DestroyVideo (VIDEO_REF vid)
if (!vid) if (!vid)
return FALSE; return FALSE;
// just some armouring; should already be stopped
TFB_StopVideo (vid);
VideoDecoder_Free (vid->decoder); VideoDecoder_Free (vid->decoder);
DestroyMutex (vid->guard); DestroyMutex (vid->guard);
HFree (vid); HFree (vid);
+1 -3
View File
@@ -23,7 +23,6 @@
#include "types.h" #include "types.h"
#include "videodec.h" #include "videodec.h"
#include "libs/sound/sound.h" #include "libs/sound/sound.h"
#include "libs/tasklib.h"
typedef struct tfb_videoclip typedef struct tfb_videoclip
@@ -36,11 +35,9 @@ typedef struct tfb_videoclip
RECT dst_rect; // destination screen rect RECT dst_rect; // destination screen rect
RECT src_rect; // source rect RECT src_rect; // source rect
MUSIC_REF hAudio; MUSIC_REF hAudio;
Task play_task;
uint32 frame_time; // time when next frame should be rendered uint32 frame_time; // time when next frame should be rendered
TFB_Image* frame; // frame preped and optimized for rendering TFB_Image* frame; // frame preped and optimized for rendering
uint32 cur_frame; // index of frame currently displayed uint32 cur_frame; // index of frame currently displayed
uint32 max_frame_wait;
bool playing; bool playing;
bool own_audio; bool own_audio;
uint32 loop_frame; // frame index to loop from uint32 loop_frame; // frame index to loop from
@@ -48,6 +45,7 @@ typedef struct tfb_videoclip
Mutex guard; Mutex guard;
uint32 want_frame; // audio-signaled desired frame index 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 void* data; // user-defined data
+1 -1
View File
@@ -74,7 +74,7 @@ struct tfb_videodecoder
uint32 w, h; uint32 w, h;
float length; // total length in seconds float length; // total length in seconds
uint32 frame_count; uint32 frame_count;
uint32 max_frame_wait; // maximum interframe delay in msecs uint32 interframe_wait; // nominal interframe delay in msecs
bool audio_synced; bool audio_synced;
// decoder callbacks // decoder callbacks
TFB_VideoCallbacks callbacks; TFB_VideoCallbacks callbacks;
+113 -144
View File
@@ -55,163 +55,130 @@ static const TFB_SoundCallbacks vp_AudioCBs =
vp_QueueBuffer vp_QueueBuffer
}; };
// inter-thread param guarded by mutex
static Semaphore vp_interthread_lock = 0;
static void* vp_interthread_clip = NULL;
bool bool
TFB_InitVideoPlayer (void) TFB_InitVideoPlayer (void)
{ {
// creation probably needs better handling // now just a stub
vp_interthread_lock = CreateSemaphore (1, "inter-thread param lock", return true;
SYNC_CLASS_VIDEO);
return vp_interthread_lock != 0;
} }
void void
TFB_UninitVideoPlayer (void) TFB_UninitVideoPlayer (void)
{ {
DestroySemaphore (vp_interthread_lock); // now just a stub
} }
// audio-synced video playback task static inline sint32
// the frame rate and timing is dictated by the audio msecToTimeCount (sint32 msec)
static int
as_video_play_task (void *data)
{ {
Task task = (Task) data; return msec * ONE_SECOND / 1000;
volatile TFB_VideoClip* vid; }
// 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; int ret;
uint32 want_frame; uint32 want_frame;
TimeCount TimeOut; uint32 prev_want_frame;
int bTerm; sint32 wait_msec;
uint32 clagged; CONTEXT oldContext;
TimeCount Now = GetTimeCounter ();
// snatch the clip pointer and release if (!vid->playing)
vid = vp_interthread_clip; return false;
vp_interthread_clip = NULL;
ClearSemaphore (vp_interthread_lock); if (Now < vid->frame_time)
return true; // not time yet
LockMutex (vid->guard); LockMutex (vid->guard);
want_frame = vid->want_frame; want_frame = vid->want_frame;
if (vid->hAudio)
PLRPlaySong (vid->hAudio, FALSE, 1);
UnlockMutex (vid->guard); UnlockMutex (vid->guard);
clagged = 0; if (want_frame >= vid->decoder->frame_count)
{
vid->playing = false;
return false;
}
// this works like so (audio-synced): // this works like so (audio-synced):
// 1. you call VideoDecoder_Seek() [when necessary] and // 1. you call VideoDecoder_Seek() [when necessary] and
// VideoDecoder_Decode() // VideoDecoder_Decode()
// 2. wait till the audio signals it's time for this frame // 2. wait till it's time for this frame to be drawn
// or the maximum inter-frame timeout elapses; the timeout // the timeout is necessary because the audio signaling is not
// is necessary because the audio signaling is not precise // precise (see vp_AudioStart, vp_AudioEnd, vp_BufferTag)
// (see vp_AudioStart, vp_AudioEnd, vp_BufferTag) // 3. output the frame; if the audio is behind, the lag counter
// 3. output the frame; if the timeout elapsed, increment the // goes up; if the video is behind, the lag counter goes down
// the lag counter // 4. set the next frame timeout; lag counter increases or
// 4. set the next frame timeout; lag counter increases the // decreases the timeout to allow audio or video to catch up
// timeout to allow audio to catch up
// 5. on a seek operation, the audio stream is moved to the // 5. on a seek operation, the audio stream is moved to the
// correct position and then the audio signals the frame // correct position and then the audio signals the frame
// that should be rendered // that should be rendered
// The system of timeouts and lag counts should make the video // The system of timeouts and lag counts should make the video
// *relatively* smooth // *relatively* smooth
// //
ret = VideoDecoder_Decode (vid->decoder); prev_want_frame = vid->cur_frame - vid->lag_cnt;
TimeOut = GetTimeCounter () + vid->decoder->max_frame_wait * if (want_frame > prev_want_frame - MAX_FRAME_LAG
ONE_SECOND / 1000; && want_frame <= prev_want_frame + MAX_FRAME_LAG)
while (!(bTerm = Task_ReadState (task, TASK_EXIT)) && ret > 0)
{ {
// wait till its time to render next frame // we will draw the next frame right now, thus +1
while (!(bTerm = Task_ReadState (task, TASK_EXIT)) vid->lag_cnt = vid->cur_frame + 1 - want_frame;
&& 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);
} }
if (vid->hAudio) else
PLRStop (vid->hAudio); { // out of sequence frame, let's get it
vid->playing = false; 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 // the frame rate and timing is dictated by the video decoder
static int static bool
video_play_task (void *data) processMuteFrame (VIDEO_REF vid)
{ {
Task task = (Task) data;
TFB_VideoClip* vid;
int ret; int ret;
TimeCount Now = GetTimeCounter ();
// snatch the clip pointer and release if (!vid->playing)
vid = vp_interthread_clip; return false;
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);
// this works like so: // this works like so:
// 1. you call VideoDecoder_Seek() [when necessary] and // 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 // On a seek operation, the decoder should reset its internal
// clock and call vp_GetTicks() again // clock and call vp_GetTicks() again
// //
ret = VideoDecoder_Decode (vid->decoder); if (Now >= vid->frame_time)
while (!Task_ReadState (task, TASK_EXIT) && ret > 0)
{ {
CONTEXT oldContext; CONTEXT oldContext;
// wait till its time to render next frame
SleepThreadUntil (vid->frame_time);
vid->cur_frame = vid->decoder->cur_frame; vid->cur_frame = vid->decoder->cur_frame;
LockMutex (GraphicsLock); LockMutex (GraphicsLock);
@@ -246,14 +209,11 @@ video_play_task (void *data)
VideoDecoder_SeekFrame (vid->decoder, vid->loop_to); VideoDecoder_SeekFrame (vid->decoder, vid->loop_to);
ret = VideoDecoder_Decode (vid->decoder); ret = VideoDecoder_Decode (vid->decoder);
if (ret <= 0)
vid->playing = false;
} }
vid->playing = false;
if (vid->hAudio)
PLRStop (vid->hAudio);
FinishTask (task); return vid->playing;
return 0;
} }
bool bool
@@ -264,6 +224,8 @@ TFB_PlayVideo (VIDEO_REF vid, uint32 x, uint32 y)
RECT vid_r = {{0, 0}, {ScreenWidth, ScreenHeight}}; RECT vid_r = {{0, 0}, {ScreenWidth, ScreenHeight}};
RECT dr = {{x, y}, {vid->w, vid->h}}; RECT dr = {{x, y}, {vid->w, vid->h}};
RECT sr; RECT sr;
bool loop_music = false;
int ret;
if (!vid) if (!vid)
return false; return false;
@@ -318,24 +280,21 @@ TFB_PlayVideo (VIDEO_REF vid, uint32 x, uint32 y)
TFB_SetSoundSampleData (*vid->hAudio, (intptr_t)vid); TFB_SetSoundSampleData (*vid->hAudio, (intptr_t)vid);
} }
SetSemaphore (vp_interthread_lock); // get the first frame
vp_interthread_clip = vid; ret = VideoDecoder_Decode (vid->decoder);
if (ret < 0)
return false;
vid->playing = true; 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) 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; // draw the first frame now
ClearSemaphore (vp_interthread_lock); vid->frame_time = GetTimeCounter ();
TFB_StopVideo (vid);
return false;
} }
return true; return true;
@@ -348,8 +307,6 @@ TFB_StopVideo (VIDEO_REF vid)
return; return;
vid->playing = false; vid->playing = false;
if (vid->play_task)
ConcludeTask (vid->play_task);
if (vid->hAudio) if (vid->hAudio)
{ {
@@ -377,6 +334,18 @@ TFB_VideoPlaying (VIDEO_REF vid)
return vid->playing; 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 uint32
TFB_GetVideoPosition (VIDEO_REF vid) TFB_GetVideoPosition (VIDEO_REF vid)
{ {
+1
View File
@@ -24,6 +24,7 @@ extern void TFB_UninitVideoPlayer (void);
extern bool TFB_PlayVideo (VIDEO_REF VidRef, uint32 x, uint32 y); extern bool TFB_PlayVideo (VIDEO_REF VidRef, uint32 x, uint32 y);
extern void TFB_StopVideo (VIDEO_REF VidRef); extern void TFB_StopVideo (VIDEO_REF VidRef);
extern bool TFB_VideoPlaying (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 uint32 TFB_GetVideoPosition (VIDEO_REF VidRef);
extern bool TFB_SeekVideo (VIDEO_REF VidRef, uint32 pos); extern bool TFB_SeekVideo (VIDEO_REF VidRef, uint32 pos);
+1
View File
@@ -47,6 +47,7 @@ extern VIDEO_TYPE VidPlayEx (VIDEO_REF VidRef, MUSIC_REF AudRef,
#define VID_NO_LOOP (0U-1) #define VID_NO_LOOP (0U-1)
extern void VidStop (void); extern void VidStop (void);
extern VIDEO_REF VidPlaying (void); extern VIDEO_REF VidPlaying (void);
extern BOOLEAN VidProcessFrame (void);
extern DWORD VidGetPosition (void); // position in milliseconds extern DWORD VidGetPosition (void); // position in milliseconds
extern BOOLEAN VidSeek (DWORD pos); // position in milliseconds extern BOOLEAN VidSeek (DWORD pos); // position in milliseconds
+4 -4
View File
@@ -822,8 +822,6 @@ DoVideoInput (void *pIS)
if (!PlayingLegacyVideo (pVIS->CurVideo)) if (!PlayingLegacyVideo (pVIS->CurVideo))
{ // Video probably finished { // Video probably finished
// Have to call VidStop anyway to cleanup
VidStop ();
return FALSE; return FALSE;
} }
@@ -832,7 +830,6 @@ DoVideoInput (void *pIS)
|| PulsedInputState.menu[KEY_MENU_SPECIAL] || PulsedInputState.menu[KEY_MENU_SPECIAL]
|| (GLOBAL (CurrentActivity) & CHECK_ABORT)) || (GLOBAL (CurrentActivity) & CHECK_ABORT))
{ // abort movie { // abort movie
VidStop ();
return FALSE; return FALSE;
} }
else if (PulsedInputState.menu[KEY_MENU_LEFT] else if (PulsedInputState.menu[KEY_MENU_LEFT]
@@ -850,7 +847,10 @@ DoVideoInput (void *pIS)
} }
else else
{ {
SleepThread (ONE_SECOND / 30); if (!VidProcessFrame ())
return FALSE;
SleepThread (ONE_SECOND / 40);
} }
return TRUE; return TRUE;