Skip to content

Commit

Permalink
Engine: BlockingVideoPlayer uses video core
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-mogilko committed Mar 14, 2024
1 parent a1e1ea4 commit 8e144cb
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 105 deletions.
170 changes: 66 additions & 104 deletions Engine/media/video/video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
#include "media/video/video.h"

#ifndef AGS_NO_VIDEO_PLAYER
#include <chrono>
#include <mutex>
#include <thread>
#include "core/assetmanager.h"
#include "ac/draw.h"
#include "ac/game.h"
Expand All @@ -29,10 +26,9 @@
#include "gfx/graphicsdriver.h"
#include "main/game_run.h"
#include "media/audio/audio.h"
#include "media/video/videoplayer.h"
#include "media/video/flic_player.h"
#include "media/video/theora_player.h"
#include "media/video/video_core.h"
#include "util/memory_compat.h"
#include "util/path.h"

using namespace AGS::Common;
using namespace AGS::Engine;
Expand All @@ -55,7 +51,7 @@ namespace Engine
class BlockingVideoPlayer : public GameState
{
public:
BlockingVideoPlayer(std::unique_ptr<VideoPlayer> player,
BlockingVideoPlayer(int player_id,
int video_flags, int state_flags, VideoSkipType skip);
~BlockingVideoPlayer();

Expand All @@ -70,27 +66,22 @@ class BlockingVideoPlayer : public GameState
void Resume() override;

private:
#if !defined(AGS_DISABLE_THREADS)
static void PollVideo(BlockingVideoPlayer *self);
#endif
void StopVideo();

std::unique_ptr<VideoPlayer> _player;
int _playerID = 0;
const String _assetName; // for diagnostics
int _videoFlags = 0;
int _stateFlags = 0;
IDriverDependantBitmap *_videoDDB = nullptr;
Rect _dstRect;
float _oldFps = 0.f;
VideoSkipType _skip = VideoSkipNone;
std::thread _videoThread;
std::mutex _videoMutex;
PlaybackState _playbackState = PlayStateInvalid;
};

BlockingVideoPlayer::BlockingVideoPlayer(std::unique_ptr<VideoPlayer> player,
BlockingVideoPlayer::BlockingVideoPlayer(int player_id,
int video_flags, int state_flags, VideoSkipType skip)
: _player(std::move(player))
: _playerID(player_id)
, _videoFlags(video_flags)
, _stateFlags(state_flags)
, _skip(skip)
Expand All @@ -104,19 +95,42 @@ BlockingVideoPlayer::~BlockingVideoPlayer()

void BlockingVideoPlayer::Begin()
{
assert(_player);
assert(_playerID >= 0);

// Optionally stop the game audio
if ((_stateFlags & kVideoState_StopGameAudio) != 0)
{
stop_all_sound_and_music();
}

// Clear the screen before starting playback
// TODO: needed for FLIC, but perhaps may be done differently
if ((_stateFlags & kVideoState_ClearScreen) != 0)
{
if (gfxDriver->UsesMemoryBackBuffer())
{
gfxDriver->GetMemoryBackBuffer()->Clear();
}
render_to_screen();
}

auto player = video_core_get_player(_playerID);

// Optionally adjust game speed, but only if it's lower than video's FPS
auto video_fps = player->GetFramerate();
auto game_fps = get_game_speed();
_oldFps = game_fps;
if (((_stateFlags & kVideoState_SetGameFps) != 0) &&
(game_fps < video_fps))
{
set_game_speed(video_fps);
}

// Setup video
if ((_videoFlags & kVideo_EnableVideo) != 0)
{
const bool software_draw = gfxDriver->HasAcceleratedTransform();
Size frame_sz = _player->GetFrameSize();
const bool software_draw = !gfxDriver->HasAcceleratedTransform();
Size frame_sz = player->GetFrameSize();
Rect dest = PlaceInRect(play.GetMainViewport(), RectWH(frame_sz),
((_stateFlags & kVideoState_Stretch) == 0) ? kPlaceCenter : kPlaceStretchProportional);
// override the stretch option if necessary
Expand All @@ -129,10 +143,10 @@ void BlockingVideoPlayer::Begin()
// because texture-based ones can scale the texture themselves.
if (software_draw && (frame_sz != dest.GetSize()))
{
_player->SetTargetFrame(dest.GetSize());
player->SetTargetFrame(dest.GetSize());
}

const int dst_depth = _player->GetTargetDepth();
const int dst_depth = player->GetTargetDepth();
if (!software_draw || ((_stateFlags & kVideoState_Stretch) == 0))
{
_videoDDB = gfxDriver->CreateDDB(frame_sz.Width, frame_sz.Height, dst_depth, true);
Expand All @@ -144,32 +158,8 @@ void BlockingVideoPlayer::Begin()
_dstRect = dest;
}

// Clear the screen before starting playback
// TODO: needed for FLIC, but perhaps may be done differently
if ((_stateFlags & kVideoState_ClearScreen) != 0)
{
if (gfxDriver->UsesMemoryBackBuffer())
{
gfxDriver->GetMemoryBackBuffer()->Clear();
}
render_to_screen();
}

auto video_fps = _player->GetFramerate();
auto game_fps = get_game_speed();
_oldFps = game_fps;
if (((_stateFlags & kVideoState_SetGameFps) != 0) &&
(game_fps < video_fps))
{
set_game_speed(video_fps);
}

_player->Play();
_playbackState = _player->GetPlayState();

#if !defined(AGS_DISABLE_THREADS)
_videoThread = std::thread(BlockingVideoPlayer::PollVideo, this);
#endif
player->Play();
_playbackState = player->GetPlayState();
}

void BlockingVideoPlayer::End()
Expand Down Expand Up @@ -202,22 +192,22 @@ void BlockingVideoPlayer::End()

void BlockingVideoPlayer::Pause()
{
assert(_player);
std::lock_guard<std::mutex> lk(_videoMutex);
_player->Pause();
assert(_playerID >= 0);
auto player = video_core_get_player(_playerID);
player->Pause();
}

void BlockingVideoPlayer::Resume()
{
assert(_player);
std::lock_guard<std::mutex> lk(_videoMutex);
_player->Play();
assert(_playerID >= 0);
auto player = video_core_get_player(_playerID);
player->Play();
}

bool BlockingVideoPlayer::Run()
{
assert(_player);
if (!_player)
assert(_playerID >= 0);
if (_playerID < 0)
return false;
// Loop until finished or skipped by player
if (IsPlaybackDone(GetPlayState()))
Expand All @@ -228,17 +218,12 @@ bool BlockingVideoPlayer::Run()
if (video_check_user_input(_skip))
return false;

#if defined(AGS_DISABLE_THREADS)
if (!_player->Poll())
return false;
#endif

// update/render next frame
std::unique_ptr<Bitmap> frame;
{
std::lock_guard<std::mutex> lk(_videoMutex);
_playbackState = _player->GetPlayState();
frame = _player->GetReadyFrame();
auto player = video_core_get_player(_playerID);
// update/render next frame
_playbackState = player->GetPlayState();
frame = player->GetReadyFrame();
}

if ((_videoFlags & kVideo_EnableVideo) != 0)
Expand All @@ -249,8 +234,8 @@ bool BlockingVideoPlayer::Run()
_videoDDB->SetStretch(_dstRect.GetWidth(), _dstRect.GetHeight(), false);

{
std::lock_guard<std::mutex> lk(_videoMutex);
_player->ReleaseFrame(std::move(frame));
auto player = video_core_get_player(_playerID);
player->ReleaseFrame(std::move(frame));
}
}
}
Expand All @@ -276,41 +261,17 @@ void BlockingVideoPlayer::Draw()
void BlockingVideoPlayer::StopVideo()
{
// Stop player and wait for the thread to stop
if (_player)
if (_playerID >= 0)
{
std::lock_guard<std::mutex> lk(_videoMutex);
_player->Stop();
video_core_slot_stop(_playerID);
_playerID = -1;
}
#if !defined(AGS_DISABLE_THREADS)
if (_videoThread.joinable())
_videoThread.join();
#endif

if (_videoDDB)
gfxDriver->DestroyDDB(_videoDDB);
_videoDDB = nullptr;
}

#if !defined(AGS_DISABLE_THREADS)
/* static */ void BlockingVideoPlayer::PollVideo(BlockingVideoPlayer *self)
{
assert(self && self->_player.get());
if (!self || !self->_player.get())
return;

bool do_run = true;
while (do_run)
{
{
std::lock_guard<std::mutex> lk(self->_videoMutex);
self->_player->Poll();
do_run = IsPlaybackReady(self->_player->GetPlayState());
}
std::this_thread::sleep_for(std::chrono::milliseconds(8));
}
}
#endif

std::unique_ptr<BlockingVideoPlayer> gl_Video;

} // namespace Engine
Expand Down Expand Up @@ -354,27 +315,27 @@ static bool video_check_user_input(VideoSkipType skip)
return false;
}

static HError video_single_run(std::unique_ptr<VideoPlayer> video, const String &asset_name,
static HError video_single_run(const String &asset_name,
int video_flags, int state_flags, VideoSkipType skip)
{
assert(video);
if (!video)
return HError::None();

std::unique_ptr<Stream> video_stream(AssetMgr->OpenAsset(asset_name));
if (!video_stream)
{
return new Error(String::FromFormat("Failed to open file: %s", asset_name.GetCStr()));
}

const int dst_depth = game.GetColorDepth();
HError err = video->Open(std::move(video_stream), asset_name, video_flags, Size(), dst_depth);
if (!err)
VideoInitParams params;
params.Flags = static_cast<VideoFlags>(video_flags);
params.TargetColorDepth = game.GetColorDepth();

video_core_init();
auto slot = video_core_slot_init(std::move(video_stream), asset_name, Path::GetFileExtension(asset_name), params);
if (slot < 0)
{
return new Error(String::FromFormat("Failed to run video %s", asset_name.GetCStr()), err);
return new Error(String::FromFormat("Failed to run video: %s", asset_name.GetCStr()));
}

gl_Video.reset(new BlockingVideoPlayer(std::move(video), video_flags, state_flags, skip));
gl_Video.reset(new BlockingVideoPlayer(slot, video_flags, state_flags, skip));
gl_Video->Begin();
while (gl_Video->Run());
gl_Video->End();
Expand All @@ -394,12 +355,12 @@ HError play_flc_video(int numb, int video_flags, int state_flags, VideoSkipType
return new Error(String::FromFormat("FLIC animation flic%d.flc nor flic%d.fli were found", numb, numb));
}

return video_single_run(std::make_unique<FlicPlayer>(), flicname, video_flags, state_flags, skip);
return video_single_run(flicname, video_flags, state_flags, skip);
}

HError play_theora_video(const char *name, int video_flags, int state_flags, VideoSkipType skip)
{
return video_single_run(std::make_unique<TheoraPlayer>(), name, video_flags, state_flags, skip);
return video_single_run(name, video_flags, state_flags, skip);
}

void video_single_pause()
Expand All @@ -422,6 +383,7 @@ void video_single_stop()
void video_shutdown()
{
video_single_stop();
video_core_shutdown();
}

#else
Expand Down
14 changes: 13 additions & 1 deletion Engine/media/video/video_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <unordered_map>
#include "debug/out.h"
#include "media/video/videoplayer.h"
#include "media/video/flic_player.h"
#include "media/video/theora_player.h"

using namespace AGS::Common;
Expand Down Expand Up @@ -101,7 +102,10 @@ static std::unique_ptr<VideoPlayer> create_video_player(const AGS::Common::Strin
{
std::unique_ptr<VideoPlayer> player;
// Table of video format detection
if (ext_hint.CompareNoCase("ogv") == 0)
if (ext_hint.CompareNoCase("flc") == 0 ||
ext_hint.CompareNoCase("fli") == 0)
player.reset(new FlicPlayer());
else if (ext_hint.CompareNoCase("ogv") == 0)
player.reset(new TheoraPlayer());
else
return nullptr; // not supported
Expand All @@ -128,6 +132,14 @@ VideoPlayerLock video_core_get_player(int slot_handle)
return VideoPlayerLock(it->second.get(), std::move(ulk), &g_vcore.poll_cv);
}

void video_core_slot_stop(int slot_handle)
{
std::lock_guard<std::mutex> lk(g_vcore.poll_mutex_m);
g_vcore.slots_[slot_handle]->Stop();
g_vcore.slots_.erase(slot_handle);
g_vcore.poll_cv.notify_all();
}

// -------------------------------------------------------------------------------------------------
// VIDEO PROCESSING
// -------------------------------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions Engine/media/video/video_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ int video_core_slot_init(std::unique_ptr<AGS::Common::Stream> in,
const AGS::Common::String &name, const AGS::Common::String &ext_hint, const VideoInitParams &params);
// Returns a VideoPlayer from the given slot, wrapped in a auto-locking struct.
VideoPlayerLock video_core_get_player(int slot_handle);
// Stop and release the video player at the given slot
void video_core_slot_stop(int slot_handle);

#if defined(AGS_DISABLE_THREADS)
// Polls the video core if we have no threads, polled in WaitForNextFrame()
Expand Down
1 change: 1 addition & 0 deletions Engine/util/threading.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class LockedObjectPtr
_cv->notify_all();
}

operator bool() const { return _object != nullptr; }
const T *operator ->() const { return _object; }
T *operator ->() { return _object; }
const T &operator *() const { return *_object; }
Expand Down

0 comments on commit 8e144cb

Please sign in to comment.