mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-09-02 12:13:10 +02:00
Implement Graphics.play_movie
in libretro builds
This commit is contained in:
parent
23bc6625f1
commit
74e5cc763c
10 changed files with 301 additions and 86 deletions
|
@ -328,6 +328,7 @@ static VALUE play_movie(int32_t argc, wasm_ptr_t argv, VALUE self) {
|
||||||
struct coro : boost::asio::coroutine {
|
struct coro : boost::asio::coroutine {
|
||||||
wasm_ptr_t str;
|
wasm_ptr_t str;
|
||||||
int32_t volume;
|
int32_t volume;
|
||||||
|
bool skippable;
|
||||||
|
|
||||||
VALUE operator()(int32_t argc, wasm_ptr_t argv, VALUE self) {
|
VALUE operator()(int32_t argc, wasm_ptr_t argv, VALUE self) {
|
||||||
BOOST_ASIO_CORO_REENTER (this) {
|
BOOST_ASIO_CORO_REENTER (this) {
|
||||||
|
@ -338,13 +339,21 @@ static VALUE play_movie(int32_t argc, wasm_ptr_t argv, VALUE self) {
|
||||||
} else {
|
} else {
|
||||||
volume = 100;
|
volume = 100;
|
||||||
}
|
}
|
||||||
bool skippable = argc >= 3 ? SANDBOX_VALUE_TO_BOOL(((VALUE *)(**sb() + argv))[2]) : false;
|
skippable = argc >= 3 ? SANDBOX_VALUE_TO_BOOL(((VALUE *)(**sb() + argv))[2]) : false;
|
||||||
// TODO: use SANDBOX_YIELD as required
|
|
||||||
GFX_GUARD_EXC(shState->graphics().playMovie((const char *)(**sb() + str), volume, skippable));
|
GFX_GUARD_EXC(sb().set_movie(shState->graphics().playMovie((const char *)(**sb() + str), volume, skippable)););
|
||||||
|
while (sb().get_movie_from_main_thread() != nullptr) {
|
||||||
|
SANDBOX_YIELD;
|
||||||
|
GFX_GUARD_EXC(sb().set_movie(shState->graphics().playMovie(sb().get_movie_from_main_thread())););
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SANDBOX_NIL;
|
return SANDBOX_NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~coro() {
|
||||||
|
GFX_GUARD_EXC(sb().set_movie(nullptr););
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return sb()->bind<struct coro>()()(argc, argv, self);
|
return sb()->bind<struct coro>()()(argc, argv, self);
|
||||||
|
|
|
@ -57,7 +57,7 @@ void sandbox::sandbox_free(usize ptr) {
|
||||||
w2c_ruby_mkxp_sandbox_free(RB, ptr);
|
w2c_ruby_mkxp_sandbox_free(RB, ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings(ruby), yielding(false), transitioning(false) {
|
sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings(ruby), movie(nullptr), yielding(false), transitioning(false) {
|
||||||
// Initialize the sandbox
|
// Initialize the sandbox
|
||||||
wasm2c_ruby_instantiate(RB, wasi.get());
|
wasm2c_ruby_instantiate(RB, wasi.get());
|
||||||
w2c_ruby_mkxp_sandbox_init(
|
w2c_ruby_mkxp_sandbox_init(
|
||||||
|
@ -133,9 +133,34 @@ sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
sandbox::~sandbox() {
|
sandbox::~sandbox() {
|
||||||
|
set_movie(nullptr);
|
||||||
if (yielding) {
|
if (yielding) {
|
||||||
w2c_ruby_asyncify_stop_unwind(ruby.get());
|
w2c_ruby_asyncify_stop_unwind(ruby.get());
|
||||||
}
|
}
|
||||||
bindings.reset(); // Destroy the bindings before destroying the runtime since the bindings destructor requires the runtime to be alive
|
bindings.reset(); // Destroy the bindings before destroying the runtime since the bindings destructor requires the runtime to be alive
|
||||||
wasm2c_ruby_free(RB);
|
wasm2c_ruby_free(RB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Movie *sandbox::get_movie_from_main_thread() {
|
||||||
|
return movie.load(std::memory_order_relaxed); // No need for synchronization because we always set the movie from the main thread
|
||||||
|
}
|
||||||
|
|
||||||
|
Movie *sandbox::get_movie_from_audio_thread() {
|
||||||
|
return movie.load(std::memory_order_seq_cst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sandbox::set_movie(Movie *new_movie) {
|
||||||
|
Movie *old_movie = get_movie_from_main_thread();
|
||||||
|
if (old_movie == new_movie) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (old_movie != nullptr) {
|
||||||
|
movie.store(nullptr, std::memory_order_seq_cst);
|
||||||
|
AudioMutexGuard guard(movie_mutex);
|
||||||
|
Graphics::stopMovie(old_movie);
|
||||||
|
}
|
||||||
|
if (new_movie != nullptr) {
|
||||||
|
movie.store(new_movie, std::memory_order_seq_cst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,13 @@
|
||||||
#ifndef MKXPZ_SANDBOX_H
|
#ifndef MKXPZ_SANDBOX_H
|
||||||
#define MKXPZ_SANDBOX_H
|
#define MKXPZ_SANDBOX_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <mkxp-sandbox-bindgen.h>
|
#include <mkxp-sandbox-bindgen.h>
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "graphics.h"
|
||||||
|
|
||||||
#define SANDBOX_AWAIT(coroutine, ...) \
|
#define SANDBOX_AWAIT(coroutine, ...) \
|
||||||
do { \
|
do { \
|
||||||
|
@ -78,16 +81,21 @@ namespace mkxp_sandbox {
|
||||||
std::shared_ptr<struct w2c_ruby> ruby;
|
std::shared_ptr<struct w2c_ruby> ruby;
|
||||||
std::unique_ptr<struct w2c_wasi__snapshot__preview1> wasi;
|
std::unique_ptr<struct w2c_wasi__snapshot__preview1> wasi;
|
||||||
boost::optional<struct mkxp_sandbox::bindings> bindings;
|
boost::optional<struct mkxp_sandbox::bindings> bindings;
|
||||||
|
std::atomic<Movie *> movie;
|
||||||
bool yielding;
|
bool yielding;
|
||||||
usize sandbox_malloc(usize size);
|
usize sandbox_malloc(usize size);
|
||||||
void sandbox_free(usize ptr);
|
void sandbox_free(usize ptr);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
AudioMutex movie_mutex;
|
||||||
bool transitioning;
|
bool transitioning;
|
||||||
inline struct mkxp_sandbox::bindings &operator*() noexcept { return *bindings; }
|
inline struct mkxp_sandbox::bindings &operator*() noexcept { return *bindings; }
|
||||||
inline struct mkxp_sandbox::bindings *operator->() noexcept { return &*bindings; }
|
inline struct mkxp_sandbox::bindings *operator->() noexcept { return &*bindings; }
|
||||||
sandbox();
|
sandbox();
|
||||||
~sandbox();
|
~sandbox();
|
||||||
|
Movie *get_movie_from_main_thread();
|
||||||
|
Movie *get_movie_from_audio_thread();
|
||||||
|
void set_movie(Movie *new_movie);
|
||||||
|
|
||||||
// Internal utility method of the `SANDBOX_YIELD` macro.
|
// Internal utility method of the `SANDBOX_YIELD` macro.
|
||||||
inline void _begin_yield() {
|
inline void _begin_yield() {
|
||||||
|
|
12
meson.build
12
meson.build
|
@ -192,6 +192,18 @@ if not compilers['cpp'].has_header_symbol('thread', 'std::this_thread::yield')
|
||||||
global_args += '-DMKXPZ_NO_STD_THIS_THREAD_YIELD'
|
global_args += '-DMKXPZ_NO_STD_THIS_THREAD_YIELD'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if not compilers['cpp'].has_header_symbol('thread', 'std::this_thread::sleep_for')
|
||||||
|
global_args += '-DMKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR'
|
||||||
|
|
||||||
|
if not compilers['cpp'].has_header_symbol('unistd.h', 'usleep')
|
||||||
|
global_args += '-DMKXPZ_NO_USLEEP'
|
||||||
|
|
||||||
|
if not compilers['cpp'].has_header_symbol('time.h', 'nanosleep')
|
||||||
|
global_args += '-DMKXPZ_NO_NANOSLEEP'
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
if not compilers['cpp'].has_header('pthread.h') or not compilers['cpp'].links('''
|
if not compilers['cpp'].has_header('pthread.h') or not compilers['cpp'].links('''
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
int main(void) {
|
int main(void) {
|
||||||
|
|
|
@ -389,6 +389,13 @@ Audio::Audio(RGSSThreadData &rtData)
|
||||||
|
|
||||||
#ifdef MKXPZ_RETRO
|
#ifdef MKXPZ_RETRO
|
||||||
void Audio::render() {
|
void Audio::render() {
|
||||||
|
if (mkxp_retro::sandbox->get_movie_from_audio_thread() != nullptr) {
|
||||||
|
AudioMutexGuard guard(mkxp_retro::sandbox->movie_mutex);
|
||||||
|
/* We need to call `get_movie_from_audio_thread()` a second time to avoid race
|
||||||
|
* conditions where the movie gets destroyed after the first time we checked
|
||||||
|
* that the movie isn't null but before we lock the mutex */
|
||||||
|
Graphics::streamMovieAudioProc(mkxp_retro::sandbox->get_movie_from_audio_thread());
|
||||||
|
}
|
||||||
p->meWatchProc();
|
p->meWatchProc();
|
||||||
for (int i = 0; i < (int)p->bgmTracks.size(); i++) {
|
for (int i = 0; i < (int)p->bgmTracks.size(); i++) {
|
||||||
p->bgmTracks[i]->render();
|
p->bgmTracks[i]->render();
|
||||||
|
|
|
@ -41,14 +41,15 @@
|
||||||
#include "shader.h"
|
#include "shader.h"
|
||||||
#include "sharedstate.h"
|
#include "sharedstate.h"
|
||||||
#include "texpool.h"
|
#include "texpool.h"
|
||||||
#ifndef MKXPZ_RETRO
|
#include "theoraplay/theoraplay.h"
|
||||||
# include "theoraplay/theoraplay.h"
|
|
||||||
#endif // MKXPZ_RETRO
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "sprite.h"
|
#include "sprite.h"
|
||||||
|
|
||||||
#ifndef MKXPZ_RETRO
|
#ifdef MKXPZ_RETRO
|
||||||
|
# include <memory>
|
||||||
|
# include "mkxp-polyfill.h"
|
||||||
|
#else
|
||||||
# include <SDL.h>
|
# include <SDL.h>
|
||||||
# include <SDL_image.h>
|
# include <SDL_image.h>
|
||||||
# include <SDL_timer.h>
|
# include <SDL_timer.h>
|
||||||
|
@ -80,7 +81,6 @@
|
||||||
#define MOVIE_AUDIO_BUFFER_SIZE 2048
|
#define MOVIE_AUDIO_BUFFER_SIZE 2048
|
||||||
#define AUDIO_BUFFER_LEN_MS 2000
|
#define AUDIO_BUFFER_LEN_MS 2000
|
||||||
|
|
||||||
#ifndef MKXPZ_RETRO
|
|
||||||
typedef struct AudioQueue
|
typedef struct AudioQueue
|
||||||
{
|
{
|
||||||
const THEORAPLAY_AudioPacket *audio;
|
const THEORAPLAY_AudioPacket *audio;
|
||||||
|
@ -91,19 +91,45 @@ typedef struct AudioQueue
|
||||||
|
|
||||||
static long readMovie(THEORAPLAY_Io *io, void *buf, long buflen)
|
static long readMovie(THEORAPLAY_Io *io, void *buf, long buflen)
|
||||||
{
|
{
|
||||||
|
#ifdef MKXPZ_RETRO
|
||||||
|
std::shared_ptr<struct FileSystem::File> *f = (std::shared_ptr<struct FileSystem::File> *) io->userdata;
|
||||||
|
return (long) PHYSFS_readBytes((*f)->get(), buf, buflen);
|
||||||
|
#else
|
||||||
SDL_RWops *f = (SDL_RWops *) io->userdata;
|
SDL_RWops *f = (SDL_RWops *) io->userdata;
|
||||||
return (long) SDL_RWread(f, buf, 1, buflen);
|
return (long) SDL_RWread(f, buf, 1, buflen);
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
} // IoFopenRead
|
} // IoFopenRead
|
||||||
|
|
||||||
|
|
||||||
static void closeMovie(THEORAPLAY_Io *io)
|
static void closeMovie(THEORAPLAY_Io *io)
|
||||||
{
|
{
|
||||||
|
#ifndef MKXPZ_RETRO
|
||||||
SDL_RWops *f = (SDL_RWops *) io->userdata;
|
SDL_RWops *f = (SDL_RWops *) io->userdata;
|
||||||
SDL_RWclose(f);
|
SDL_RWclose(f);
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
free(io);
|
free(io);
|
||||||
} // IoFopenClose
|
} // IoFopenClose
|
||||||
|
|
||||||
|
|
||||||
|
static void movieSleep(uint32_t milliseconds)
|
||||||
|
{
|
||||||
|
#ifdef MKXPZ_RETRO
|
||||||
|
mkxp_sleep_ms(milliseconds);
|
||||||
|
#else
|
||||||
|
SDL_Delay(milliseconds);
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef MKXPZ_RETRO
|
||||||
|
template<class C, void (C::*func)()>
|
||||||
|
static void movieThreadFun(C *obj)
|
||||||
|
{
|
||||||
|
(obj->*func)();
|
||||||
|
}
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
|
|
||||||
|
|
||||||
struct Movie
|
struct Movie
|
||||||
{
|
{
|
||||||
THEORAPLAY_Decoder *decoder;
|
THEORAPLAY_Decoder *decoder;
|
||||||
|
@ -113,20 +139,49 @@ struct Movie
|
||||||
bool hasAudio;
|
bool hasAudio;
|
||||||
bool skippable;
|
bool skippable;
|
||||||
Bitmap *videoBitmap;
|
Bitmap *videoBitmap;
|
||||||
|
#ifdef MKXPZ_RETRO
|
||||||
|
std::shared_ptr<struct FileSystem::File> srcOps;
|
||||||
|
#else
|
||||||
SDL_RWops srcOps;
|
SDL_RWops srcOps;
|
||||||
SDL_Thread *audioThread;
|
SDL_Thread *audioThread;
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
AtomicFlag audioThreadTermReq;
|
AtomicFlag audioThreadTermReq;
|
||||||
volatile AudioQueue *audioQueueHead;
|
volatile AudioQueue *audioQueueHead;
|
||||||
volatile AudioQueue *audioQueueTail;
|
volatile AudioQueue *audioQueueTail;
|
||||||
ALuint audioSource;
|
ALuint audioSource;
|
||||||
ALuint alBuffers[STREAM_BUFS];
|
ALuint alBuffers[STREAM_BUFS];
|
||||||
ALshort audioBuffer[MOVIE_AUDIO_BUFFER_SIZE];
|
ALshort audioBuffer[MOVIE_AUDIO_BUFFER_SIZE];
|
||||||
SDL_mutex *audioMutex;
|
AudioMutex audioMutex;
|
||||||
|
Sprite *movieSprite;
|
||||||
|
Sprite *letterboxSprite;
|
||||||
|
Bitmap *letterbox;
|
||||||
|
uint64_t frameMs;
|
||||||
|
uint64_t baseTicks;
|
||||||
|
float volume;
|
||||||
|
bool openedAudio;
|
||||||
|
|
||||||
|
// Variables used by streamMovieAudioProc
|
||||||
|
ALint streamMovieAudioState;
|
||||||
|
ALint procBufs;
|
||||||
|
volatile AudioQueue *audioPacketAndOffset;
|
||||||
|
int channels;
|
||||||
|
int sampleRate;
|
||||||
|
float *sourceSamples;
|
||||||
|
ALuint samplesToProcess;
|
||||||
|
ALshort *sampleBuffer;
|
||||||
|
ALuint remainingSamples;
|
||||||
|
|
||||||
Movie(bool skippable_)
|
Movie(float volume, bool skippable_)
|
||||||
: decoder(0), audio(0), video(0), skippable(skippable_), videoBitmap(0), audioThread(0)
|
: decoder(0), audio(0), video(0), skippable(skippable_), videoBitmap(0),
|
||||||
|
#ifndef MKXPZ_RETRO
|
||||||
|
audioThread(0),
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
|
movieSprite(nullptr), letterboxSprite(nullptr), letterbox(nullptr), frameMs(0), baseTicks(-1), volume(volume), openedAudio(false),
|
||||||
|
streamMovieAudioState(0), procBufs(STREAM_BUFS)
|
||||||
{
|
{
|
||||||
|
audioThreadTermReq.set();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool preparePlayback()
|
bool preparePlayback()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -134,7 +189,9 @@ struct Movie
|
||||||
// https://ffmpeg.org/doxygen/0.11/group__lavc__misc__pixfmt.html
|
// https://ffmpeg.org/doxygen/0.11/group__lavc__misc__pixfmt.html
|
||||||
THEORAPLAY_Io *io = (THEORAPLAY_Io *) malloc(sizeof (THEORAPLAY_Io));
|
THEORAPLAY_Io *io = (THEORAPLAY_Io *) malloc(sizeof (THEORAPLAY_Io));
|
||||||
if(!io) {
|
if(!io) {
|
||||||
|
#ifndef MKXPZ_RETRO
|
||||||
SDL_RWclose(&srcOps);
|
SDL_RWclose(&srcOps);
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,13 +200,15 @@ struct Movie
|
||||||
io->userdata = &srcOps;
|
io->userdata = &srcOps;
|
||||||
decoder = THEORAPLAY_startDecode(io, DEF_MAX_VIDEO_FRAMES, THEORAPLAY_VIDFMT_RGBA);
|
decoder = THEORAPLAY_startDecode(io, DEF_MAX_VIDEO_FRAMES, THEORAPLAY_VIDFMT_RGBA);
|
||||||
if (!decoder) {
|
if (!decoder) {
|
||||||
|
#ifndef MKXPZ_RETRO
|
||||||
SDL_RWclose(&srcOps);
|
SDL_RWclose(&srcOps);
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until the decoder has parsed out some basic truths from the file.
|
// Wait until the decoder has parsed out some basic truths from the file.
|
||||||
while (!THEORAPLAY_isInitialized(decoder)) {
|
while (!THEORAPLAY_isInitialized(decoder)) {
|
||||||
SDL_Delay(VIDEO_DELAY);
|
movieSleep(VIDEO_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once we're initialized, we can tell if this file has audio and/or video.
|
// Once we're initialized, we can tell if this file has audio and/or video.
|
||||||
|
@ -162,7 +221,7 @@ struct Movie
|
||||||
if ((THEORAPLAY_availableVideo(decoder) >= DEF_MAX_VIDEO_FRAMES)) {
|
if ((THEORAPLAY_availableVideo(decoder) >= DEF_MAX_VIDEO_FRAMES)) {
|
||||||
break; // we'll never progress, there's no audio yet but we've prebuffered as much as we plan to.
|
break; // we'll never progress, there's no audio yet but we've prebuffered as much as we plan to.
|
||||||
}
|
}
|
||||||
SDL_Delay(VIDEO_DELAY);
|
movieSleep(VIDEO_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,14 +233,14 @@ struct Movie
|
||||||
|
|
||||||
// Wait until we have video
|
// Wait until we have video
|
||||||
while ((video = THEORAPLAY_getVideo(decoder)) == NULL) {
|
while ((video = THEORAPLAY_getVideo(decoder)) == NULL) {
|
||||||
SDL_Delay(VIDEO_DELAY);
|
movieSleep(VIDEO_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until we have audio, if applicable
|
// Wait until we have audio, if applicable
|
||||||
audio = NULL;
|
audio = NULL;
|
||||||
if (hasAudio) {
|
if (hasAudio) {
|
||||||
while ((audio = THEORAPLAY_getAudio(decoder)) == NULL && THEORAPLAY_availableVideo(decoder) < DEF_MAX_VIDEO_FRAMES) {
|
while ((audio = THEORAPLAY_getAudio(decoder)) == NULL && THEORAPLAY_availableVideo(decoder) < DEF_MAX_VIDEO_FRAMES) {
|
||||||
SDL_Delay(VIDEO_DELAY);
|
movieSleep(VIDEO_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create this Bitmap without a hires replacement, because we don't
|
// Create this Bitmap without a hires replacement, because we don't
|
||||||
|
@ -210,17 +269,16 @@ struct Movie
|
||||||
item->offset = 0;
|
item->offset = 0;
|
||||||
item->next = NULL;
|
item->next = NULL;
|
||||||
|
|
||||||
SDL_LockMutex(audioMutex);
|
AudioMutexGuard guard(audioMutex);
|
||||||
if (audioQueueTail) {
|
if (audioQueueTail) {
|
||||||
audioQueueTail->next = item;
|
audioQueueTail->next = item;
|
||||||
} else {
|
} else {
|
||||||
audioQueueHead = item;
|
audioQueueHead = item;
|
||||||
}
|
}
|
||||||
audioQueueTail = item;
|
audioQueueTail = item;
|
||||||
SDL_UnlockMutex(audioMutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void bufferMovieAudio(THEORAPLAY_Decoder *decoder, const Uint32 now) {
|
void bufferMovieAudio(THEORAPLAY_Decoder *decoder, const uint32_t now) {
|
||||||
const THEORAPLAY_AudioPacket *audio;
|
const THEORAPLAY_AudioPacket *audio;
|
||||||
while ((audio = THEORAPLAY_getAudio(decoder)) != NULL) {
|
while ((audio = THEORAPLAY_getAudio(decoder)) != NULL) {
|
||||||
queueAudioPacket(audio);
|
queueAudioPacket(audio);
|
||||||
|
@ -230,25 +288,18 @@ struct Movie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void streamMovieAudio(){
|
bool streamMovieAudioProc() {
|
||||||
ALint state = 0;
|
// Quit if audio thread terminate request has been made
|
||||||
ALint procBufs = STREAM_BUFS;
|
if (audioThreadTermReq) return false;
|
||||||
volatile AudioQueue *audioPacketAndOffset;
|
|
||||||
int channels;
|
|
||||||
int sampleRate;
|
|
||||||
float *sourceSamples;
|
|
||||||
ALuint samplesToProcess;
|
|
||||||
ALshort *sampleBuffer;
|
|
||||||
ALuint remainingSamples;
|
|
||||||
|
|
||||||
while(true) {
|
if(procBufs > 0) {
|
||||||
while(procBufs--) {
|
--procBufs;
|
||||||
// Quit if audio thread terminate request has been made
|
|
||||||
if (audioThreadTermReq) return;
|
|
||||||
|
|
||||||
remainingSamples = MOVIE_AUDIO_BUFFER_SIZE;
|
remainingSamples = MOVIE_AUDIO_BUFFER_SIZE;
|
||||||
sampleBuffer = audioBuffer;
|
sampleBuffer = audioBuffer;
|
||||||
SDL_LockMutex(audioMutex);
|
|
||||||
|
{
|
||||||
|
AudioMutexGuard guard(audioMutex);
|
||||||
|
|
||||||
while(audioQueueHead && (remainingSamples > 0)) {
|
while(audioQueueHead && (remainingSamples > 0)) {
|
||||||
audioPacketAndOffset = audioQueueHead;
|
audioPacketAndOffset = audioQueueHead;
|
||||||
|
@ -283,27 +334,32 @@ struct Movie
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!audioQueueHead) audioQueueTail = NULL;
|
if(!audioQueueHead) audioQueueTail = NULL;
|
||||||
|
|
||||||
SDL_UnlockMutex(audioMutex);
|
|
||||||
|
|
||||||
alBufferData(alBuffers[procBufs], channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, audioBuffer,
|
|
||||||
(MOVIE_AUDIO_BUFFER_SIZE - remainingSamples) * sizeof(ALshort), sampleRate);
|
|
||||||
alSourceQueueBuffers(audioSource, 1, &alBuffers[procBufs]);
|
|
||||||
alGetSourcei(audioSource, AL_SOURCE_STATE, &state);
|
|
||||||
if(state != AL_PLAYING) alSourcePlay(audioSource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Periodically check the buffers until one is available
|
alBufferData(alBuffers[procBufs], channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, audioBuffer,
|
||||||
while(true) {
|
(MOVIE_AUDIO_BUFFER_SIZE - remainingSamples) * sizeof(ALshort), sampleRate);
|
||||||
// Quit if audio thread terminate request has been made
|
alSourceQueueBuffers(audioSource, 1, &alBuffers[procBufs]);
|
||||||
if (audioThreadTermReq) return;
|
alGetSourcei(audioSource, AL_SOURCE_STATE, &streamMovieAudioState);
|
||||||
|
if(streamMovieAudioState != AL_PLAYING) alSourcePlay(audioSource);
|
||||||
alGetSourcei(audioSource, AL_BUFFERS_PROCESSED, &procBufs);
|
|
||||||
if(procBufs > 0) break;
|
|
||||||
SDL_Delay(AUDIO_SLEEP);
|
|
||||||
}
|
|
||||||
alSourceUnqueueBuffers(audioSource, procBufs, alBuffers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Periodically check the buffers until one is available
|
||||||
|
else {
|
||||||
|
alGetSourcei(audioSource, AL_BUFFERS_PROCESSED, &procBufs);
|
||||||
|
if(procBufs > 0) {
|
||||||
|
alSourceUnqueueBuffers(audioSource, procBufs, alBuffers);
|
||||||
|
} else {
|
||||||
|
#ifndef MKXPZ_RETRO
|
||||||
|
movieSleep(AUDIO_SLEEP);
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void streamMovieAudio() {
|
||||||
|
while(streamMovieAudioProc());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool startAudio(float volume)
|
bool startAudio(float volume)
|
||||||
|
@ -313,31 +369,45 @@ struct Movie
|
||||||
alSourcef(audioSource, AL_GAIN, volume);
|
alSourcef(audioSource, AL_GAIN, volume);
|
||||||
|
|
||||||
audioThreadTermReq.clear();
|
audioThreadTermReq.clear();
|
||||||
audioMutex = SDL_CreateMutex();
|
|
||||||
queueAudioPacket(audio);
|
queueAudioPacket(audio);
|
||||||
audio = NULL;
|
audio = NULL;
|
||||||
bufferMovieAudio(decoder, 0);
|
bufferMovieAudio(decoder, 0);
|
||||||
|
#ifndef MKXPZ_RETRO
|
||||||
audioThread = createSDLThread <Movie, &Movie::streamMovieAudio>(this, "movieaudio");
|
audioThread = createSDLThread <Movie, &Movie::streamMovieAudio>(this, "movieaudio");
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void play(float volume)
|
bool play()
|
||||||
{
|
{
|
||||||
Uint32 frameMs = 0;
|
if (baseTicks == (uint64_t)-1) {
|
||||||
Uint32 baseTicks = SDL_GetTicks();
|
#ifdef MKXPZ_RETRO
|
||||||
bool openedAudio = false;
|
baseTicks = mkxp_retro::get_ticks_ms();
|
||||||
|
#else
|
||||||
|
baseTicks = SDL_GetTicks();
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
|
}
|
||||||
while (THEORAPLAY_isDecoding(decoder)) {
|
while (THEORAPLAY_isDecoding(decoder)) {
|
||||||
// Check for reset/shutdown input
|
// Check for reset/shutdown input
|
||||||
if(shState->graphics().updateMovieInput(this)) break;
|
if(shState->graphics().updateMovieInput(this)) break;
|
||||||
|
|
||||||
// Check for attempted skip
|
// Check for attempted skip
|
||||||
if (skippable) {
|
if (skippable) {
|
||||||
|
#ifdef MKXPZ_RETRO // TODO: move into shState
|
||||||
|
mkxp_retro::input->update();
|
||||||
|
if (mkxp_retro::input->isTriggered(Input::C) || mkxp_retro::input->isTriggered(Input::B)) break;
|
||||||
|
#else
|
||||||
shState->input().update();
|
shState->input().update();
|
||||||
if (shState->input().isTriggered(Input::C) || shState->input().isTriggered(Input::B)) break;
|
if (shState->input().isTriggered(Input::C) || shState->input().isTriggered(Input::B)) break;
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
}
|
}
|
||||||
|
|
||||||
const Uint32 now = SDL_GetTicks() - baseTicks;
|
#ifdef MKXPZ_RETRO
|
||||||
|
const uint64_t now = mkxp_retro::get_ticks_ms() - baseTicks;
|
||||||
|
#else
|
||||||
|
const uint64_t now = SDL_GetTicks() - baseTicks;
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
|
|
||||||
if (!video) {
|
if (!video) {
|
||||||
video = THEORAPLAY_getVideo(decoder);
|
video = THEORAPLAY_getVideo(decoder);
|
||||||
|
@ -359,7 +429,7 @@ struct Movie
|
||||||
}
|
}
|
||||||
|
|
||||||
if (video && (video->playms <= now)) {
|
if (video && (video->playms <= now)) {
|
||||||
frameMs = (video->fps == 0.0) ? 0 : ((Uint32) (1000.0 / video->fps));
|
frameMs = (video->fps == 0.0) ? 0 : ((uint64_t) (1000.0 / video->fps));
|
||||||
if ( frameMs && ((now - video->playms) >= frameMs) )
|
if ( frameMs && ((now - video->playms) >= frameMs) )
|
||||||
{
|
{
|
||||||
// Skip frames to catch up
|
// Skip frames to catch up
|
||||||
|
@ -389,18 +459,29 @@ struct Movie
|
||||||
video = NULL;
|
video = NULL;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
#ifndef MKXPZ_RETRO
|
||||||
// Next video frame not yet ready, let the CPU breathe
|
// Next video frame not yet ready, let the CPU breathe
|
||||||
SDL_Delay(VIDEO_DELAY);
|
movieSleep(VIDEO_DELAY);
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openedAudio) {
|
if (openedAudio) {
|
||||||
bufferMovieAudio(decoder, now);
|
bufferMovieAudio(decoder, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MKXPZ_RETRO
|
||||||
|
return true;
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
~Movie()
|
~Movie()
|
||||||
{
|
{
|
||||||
|
if (letterbox) delete letterbox;
|
||||||
|
if (letterboxSprite) delete letterboxSprite;
|
||||||
|
if (movieSprite) delete movieSprite;
|
||||||
if (hasAudio) {
|
if (hasAudio) {
|
||||||
if (audioQueueTail) {
|
if (audioQueueTail) {
|
||||||
THEORAPLAY_freeAudio(audioQueueTail->audio);
|
THEORAPLAY_freeAudio(audioQueueTail->audio);
|
||||||
|
@ -411,12 +492,13 @@ struct Movie
|
||||||
THEORAPLAY_freeAudio(audioQueueHead->audio);
|
THEORAPLAY_freeAudio(audioQueueHead->audio);
|
||||||
}
|
}
|
||||||
audioQueueHead = NULL;
|
audioQueueHead = NULL;
|
||||||
SDL_DestroyMutex(audioMutex);
|
|
||||||
audioThreadTermReq.set();
|
audioThreadTermReq.set();
|
||||||
|
#ifndef MKXPZ_RETRO
|
||||||
if(audioThread) {
|
if(audioThread) {
|
||||||
SDL_WaitThread(audioThread, 0);
|
SDL_WaitThread(audioThread, 0);
|
||||||
audioThread = 0;
|
audioThread = 0;
|
||||||
}
|
}
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
alSourceStop(audioSource);
|
alSourceStop(audioSource);
|
||||||
alDeleteSources(1, &audioSource);
|
alDeleteSources(1, &audioSource);
|
||||||
alDeleteBuffers(STREAM_BUFS, alBuffers);
|
alDeleteBuffers(STREAM_BUFS, alBuffers);
|
||||||
|
@ -430,19 +512,30 @@ struct Movie
|
||||||
|
|
||||||
struct MovieOpenHandler : FileSystem::OpenHandler
|
struct MovieOpenHandler : FileSystem::OpenHandler
|
||||||
{
|
{
|
||||||
|
#ifdef MKXPZ_RETRO
|
||||||
|
std::shared_ptr<struct FileSystem::File> *srcOps;
|
||||||
|
#else
|
||||||
SDL_RWops *srcOps;
|
SDL_RWops *srcOps;
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
|
|
||||||
|
#ifdef MKXPZ_RETRO
|
||||||
|
MovieOpenHandler(std::shared_ptr<struct FileSystem::File> &srcOps)
|
||||||
|
#else
|
||||||
MovieOpenHandler(SDL_RWops &srcOps)
|
MovieOpenHandler(SDL_RWops &srcOps)
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
: srcOps(&srcOps)
|
: srcOps(&srcOps)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
#ifdef MKXPZ_RETRO
|
||||||
|
bool tryRead(std::shared_ptr<struct FileSystem::File> ops, const char *ext)
|
||||||
|
#else
|
||||||
bool tryRead(SDL_RWops &ops, const char *ext)
|
bool tryRead(SDL_RWops &ops, const char *ext)
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
{
|
{
|
||||||
*srcOps = ops;
|
*srcOps = ops;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#endif // MKXPZ_RETRO
|
|
||||||
|
|
||||||
struct PingPong {
|
struct PingPong {
|
||||||
TEXFBO rt[2];
|
TEXFBO rt[2];
|
||||||
|
@ -1634,48 +1727,80 @@ void Graphics::resizeWindow(int width, int height, bool center) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Graphics::updateMovieInput(Movie *movie) {
|
bool Graphics::updateMovieInput(Movie *movie) {
|
||||||
#ifdef MKXPZ_RETRO
|
|
||||||
return false; // TODO
|
|
||||||
#else
|
|
||||||
return p->threadData->rqTerm || p->threadData->rqReset;
|
return p->threadData->rqTerm || p->threadData->rqReset;
|
||||||
#endif // MKXPZ_RETRO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Graphics::playMovie(const char *filename, int volume_, bool skippable) {
|
Movie *Graphics::playMovie(const char *filename, int volume_, bool skippable) {
|
||||||
#ifndef MKXPZ_RETRO
|
|
||||||
if (shState->config().enableHires) {
|
if (shState->config().enableHires) {
|
||||||
Debug() << "BUG: High-res Graphics playMovie not implemented";
|
Debug() << "BUG: High-res Graphics playMovie not implemented";
|
||||||
}
|
}
|
||||||
|
|
||||||
Movie *movie = new Movie(skippable);
|
Movie *movie = new Movie(volume_, skippable);
|
||||||
MovieOpenHandler handler(movie->srcOps);
|
MovieOpenHandler handler(movie->srcOps);
|
||||||
|
#ifdef MKXPZ_RETRO // TODO: move into shState
|
||||||
|
{
|
||||||
|
std::string path("/mkxp-retro-game/");
|
||||||
|
path.append(filename);
|
||||||
|
mkxp_retro::fs->openRead(handler, path.c_str());
|
||||||
|
}
|
||||||
|
#else
|
||||||
shState->fileSystem().openRead(handler, filename);
|
shState->fileSystem().openRead(handler, filename);
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
float volume = volume_ * 0.01f;
|
float volume = volume_ * 0.01f;
|
||||||
|
|
||||||
if (movie->preparePlayback()) {
|
if (movie->preparePlayback()) {
|
||||||
Sprite movieSprite;
|
movie->movieSprite = new Sprite;
|
||||||
|
|
||||||
// Currently this stretches to fit the screen. VX Ace behavior is to center it and let the edges run off
|
// Currently this stretches to fit the screen. VX Ace behavior is to center it and let the edges run off
|
||||||
movieSprite.setBitmap(movie->videoBitmap);
|
movie->movieSprite->setBitmap(movie->videoBitmap);
|
||||||
double ratio = std::min((double)width() / movie->video->width, (double)height() / movie->video->height);
|
double ratio = std::min((double)width() / movie->video->width, (double)height() / movie->video->height);
|
||||||
movieSprite.setZoomX(ratio);
|
movie->movieSprite->setZoomX(ratio);
|
||||||
movieSprite.setZoomY(ratio);
|
movie->movieSprite->setZoomY(ratio);
|
||||||
movieSprite.setX((width() / 2) - (movie->video->width * ratio / 2));
|
movie->movieSprite->setX((width() / 2) - (movie->video->width * ratio / 2));
|
||||||
movieSprite.setY((height() / 2) - (movie->video->height * ratio / 2));
|
movie->movieSprite->setY((height() / 2) - (movie->video->height * ratio / 2));
|
||||||
|
|
||||||
Sprite letterboxSprite;
|
movie->letterboxSprite = new Sprite;
|
||||||
Bitmap letterbox(width(), height());
|
movie->letterbox = new Bitmap(width(), height());
|
||||||
letterbox.fillRect(0, 0, width(), height(), Vec4(0,0,0,255));
|
movie->letterbox->fillRect(0, 0, width(), height(), Vec4(0,0,0,255));
|
||||||
letterboxSprite.setBitmap(&letterbox);
|
movie->letterboxSprite->setBitmap(movie->letterbox);
|
||||||
|
|
||||||
letterboxSprite.setZ(4999);
|
movie->letterboxSprite->setZ(4999);
|
||||||
movieSprite.setZ(5001);
|
movie->movieSprite->setZ(5001);
|
||||||
|
|
||||||
movie->play(volume);
|
if (movie->play()) {
|
||||||
|
return movie;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef MKXPZ_RETRO
|
||||||
delete movie;
|
delete movie;
|
||||||
#endif // MKXPZ_RETRO
|
#endif // MKXPZ_RETRO
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Movie *Graphics::playMovie(Movie *movie) {
|
||||||
|
if (movie == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movie->play()) {
|
||||||
|
return movie;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef MKXPZ_RETRO
|
||||||
|
delete movie;
|
||||||
|
#endif // MKXPZ_RETRO
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Graphics::stopMovie(Movie *movie) {
|
||||||
|
if (movie != nullptr) {
|
||||||
|
delete movie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Graphics::streamMovieAudioProc(Movie *movie) {
|
||||||
|
return movie->streamMovieAudioProc();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Graphics::screenshot(const char *filename) {
|
void Graphics::screenshot(const char *filename) {
|
||||||
|
|
|
@ -73,7 +73,10 @@ public:
|
||||||
void resizeWindow(int width, int height, bool center=false);
|
void resizeWindow(int width, int height, bool center=false);
|
||||||
void drawMovieFrame(const THEORAPLAY_VideoFrame* video, Bitmap *videoBitmap);
|
void drawMovieFrame(const THEORAPLAY_VideoFrame* video, Bitmap *videoBitmap);
|
||||||
bool updateMovieInput(Movie *movie);
|
bool updateMovieInput(Movie *movie);
|
||||||
void playMovie(const char *filename, int volume, bool skippable);
|
Movie *playMovie(const char *filename, int volume, bool skippable);
|
||||||
|
Movie *playMovie(Movie *movie);
|
||||||
|
static void stopMovie(Movie *movie);
|
||||||
|
static bool streamMovieAudioProc(Movie *movie);
|
||||||
void screenshot(const char *filename);
|
void screenshot(const char *filename);
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
|
@ -406,7 +406,7 @@ main_source = files(
|
||||||
is_libretro ? 'audio/sndfilesource.cpp' : 'audio/sdlsoundsource.cpp',
|
is_libretro ? 'audio/sndfilesource.cpp' : 'audio/sdlsoundsource.cpp',
|
||||||
'audio/soundemitter.cpp',
|
'audio/soundemitter.cpp',
|
||||||
'audio/vorbissource.cpp',
|
'audio/vorbissource.cpp',
|
||||||
is_libretro ? [] : 'theoraplay/theoraplay.c',
|
'theoraplay/theoraplay.c',
|
||||||
|
|
||||||
'crypto/rgssad.cpp',
|
'crypto/rgssad.cpp',
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,15 @@
|
||||||
# include <malloc.h>
|
# include <malloc.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef MKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR
|
||||||
|
# include <thread>
|
||||||
|
#elif !defined(MKXPZ_NO_USLEEP)
|
||||||
|
# include <unistd.h>
|
||||||
|
#elif !defined(MKXPZ_NO_NANOSLEEP)
|
||||||
|
# include <time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#ifdef MKXPZ_NO_SPRINTF
|
#ifdef MKXPZ_NO_SPRINTF
|
||||||
extern "C" int sprintf(char *buffer, const char *format, ...) {
|
extern "C" int sprintf(char *buffer, const char *format, ...) {
|
||||||
va_list vlist;
|
va_list vlist;
|
||||||
|
@ -311,3 +320,17 @@ extern "C" int mkxp_sem_wait(mkxp_sem_t *sem) {
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void mkxp_sleep_ms(uint32_t milliseconds) {
|
||||||
|
#ifndef MKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR
|
||||||
|
std::this_thread::sleep_for(std::chrono::duration<uint32_t, std::milli>(milliseconds));
|
||||||
|
#elif !defined(MKXPZ_NO_USLEEP)
|
||||||
|
usleep((useconds_t)1000 * (useconds_t)milliseconds);
|
||||||
|
#elif !defined(MKXPZ_NO_NANOSLEEP)
|
||||||
|
struct timespec t;
|
||||||
|
t.tv_sec = milliseconds / 1000;
|
||||||
|
t.tv_nsec = milliseconds % 1000;
|
||||||
|
t.tv_nsec *= 1000000;
|
||||||
|
nanosleep(&t, nullptr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <tgmath.h>
|
#include <tgmath.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#ifndef MKXPZ_NO_PTHREAD_H
|
#ifndef MKXPZ_NO_PTHREAD_H
|
||||||
|
@ -131,6 +132,8 @@ int mkxp_sem_post(mkxp_sem_t *sem);
|
||||||
|
|
||||||
int mkxp_sem_wait(mkxp_sem_t *sem);
|
int mkxp_sem_wait(mkxp_sem_t *sem);
|
||||||
|
|
||||||
|
void mkxp_sleep_ms(uint32_t milliseconds);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue