mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-09-01 03:33:08 +02:00
Revamp movie playback
Separated movie playback code Load movie file via SDL, allowing any file extension Removed dead audio code
This commit is contained in:
parent
bdb7c77a75
commit
86a6c93d58
4 changed files with 355 additions and 344 deletions
|
@ -39,8 +39,6 @@ struct AudioPrivate
|
||||||
AudioStream bgs;
|
AudioStream bgs;
|
||||||
AudioStream me;
|
AudioStream me;
|
||||||
|
|
||||||
AudioStream movie;
|
|
||||||
|
|
||||||
SoundEmitter se;
|
SoundEmitter se;
|
||||||
|
|
||||||
SyncPoint &syncPoint;
|
SyncPoint &syncPoint;
|
||||||
|
@ -68,7 +66,6 @@ struct AudioPrivate
|
||||||
AudioPrivate(RGSSThreadData &rtData)
|
AudioPrivate(RGSSThreadData &rtData)
|
||||||
: bgm(ALStream::Looped, "bgm"),
|
: bgm(ALStream::Looped, "bgm"),
|
||||||
bgs(ALStream::Looped, "bgs"),
|
bgs(ALStream::Looped, "bgs"),
|
||||||
movie(ALStream::NotLooped, "movie"),
|
|
||||||
me(ALStream::NotLooped, "me"),
|
me(ALStream::NotLooped, "me"),
|
||||||
se(rtData.config),
|
se(rtData.config),
|
||||||
syncPoint(rtData.syncPoint)
|
syncPoint(rtData.syncPoint)
|
||||||
|
@ -313,31 +310,6 @@ void Audio::seStop()
|
||||||
p->se.stop();
|
p->se.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Audio::moviePlay(const char *filename,
|
|
||||||
int volume,
|
|
||||||
int pitch,
|
|
||||||
float pos)
|
|
||||||
{
|
|
||||||
p->movie.play(filename, volume, pitch, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::movieSeek(float pos)
|
|
||||||
{
|
|
||||||
p->movie.seek(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::movieStop()
|
|
||||||
{
|
|
||||||
p->movie.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::movieFade(int time)
|
|
||||||
{
|
|
||||||
p->movie.fadeOut(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Audio::setupMidi()
|
void Audio::setupMidi()
|
||||||
{
|
{
|
||||||
shState->midiState().initIfNeeded(shState->config());
|
shState->midiState().initIfNeeded(shState->config());
|
||||||
|
@ -353,11 +325,6 @@ float Audio::bgsPos()
|
||||||
return p->bgs.playingOffset();
|
return p->bgs.playingOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
float Audio::moviePos()
|
|
||||||
{
|
|
||||||
return p->movie.playingOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::reset()
|
void Audio::reset()
|
||||||
{
|
{
|
||||||
p->bgm.stop();
|
p->bgm.stop();
|
||||||
|
|
|
@ -63,18 +63,9 @@ public:
|
||||||
int pitch = 100);
|
int pitch = 100);
|
||||||
void seStop();
|
void seStop();
|
||||||
|
|
||||||
void moviePlay(const char *filename,
|
|
||||||
int volume = 100,
|
|
||||||
int pitch = 100,
|
|
||||||
float pos = 0);
|
|
||||||
void movieStop();
|
|
||||||
void movieFade(int time);
|
|
||||||
void movieSeek(float pos);
|
|
||||||
|
|
||||||
void setupMidi();
|
void setupMidi();
|
||||||
float bgmPos();
|
float bgmPos();
|
||||||
float bgsPos();
|
float bgsPos();
|
||||||
float moviePos();
|
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,318 @@ typedef struct AudioQueue
|
||||||
struct AudioQueue *next;
|
struct AudioQueue *next;
|
||||||
} AudioQueue;
|
} AudioQueue;
|
||||||
|
|
||||||
|
static volatile AudioQueue *movieAudioQueue;
|
||||||
|
static volatile AudioQueue *movieAudioQueueTail;
|
||||||
|
|
||||||
|
|
||||||
|
static long readMovie(THEORAPLAY_Io *io, void *buf, long buflen)
|
||||||
|
{
|
||||||
|
SDL_RWops *f = (SDL_RWops *) io->userdata;
|
||||||
|
return (long) SDL_RWread(f, buf, 1, buflen);
|
||||||
|
} // IoFopenRead
|
||||||
|
|
||||||
|
|
||||||
|
static void closeMovie(THEORAPLAY_Io *io)
|
||||||
|
{
|
||||||
|
SDL_RWops *f = (SDL_RWops *) io->userdata;
|
||||||
|
SDL_RWclose(f);
|
||||||
|
free(io);
|
||||||
|
} // IoFopenClose
|
||||||
|
|
||||||
|
|
||||||
|
struct Movie
|
||||||
|
{
|
||||||
|
THEORAPLAY_Decoder *decoder;
|
||||||
|
const THEORAPLAY_AudioPacket *audio;
|
||||||
|
const THEORAPLAY_VideoFrame *video;
|
||||||
|
bool hasVideo;
|
||||||
|
bool hasAudio;
|
||||||
|
Bitmap *videoBitmap;
|
||||||
|
SDL_RWops srcOps;
|
||||||
|
|
||||||
|
Movie()
|
||||||
|
: decoder(0), audio(0), video(0)
|
||||||
|
{}
|
||||||
|
bool preparePlayback()
|
||||||
|
{
|
||||||
|
|
||||||
|
// https://theora.org/doc/libtheora-1.0/codec_8h.html
|
||||||
|
// https://ffmpeg.org/doxygen/0.11/group__lavc__misc__pixfmt.html
|
||||||
|
THEORAPLAY_Io *io = (THEORAPLAY_Io *) malloc(sizeof (THEORAPLAY_Io));
|
||||||
|
if(!io) {
|
||||||
|
SDL_RWclose(&srcOps);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
io->read = readMovie;
|
||||||
|
io->close = closeMovie;
|
||||||
|
io->userdata = &srcOps;
|
||||||
|
decoder = THEORAPLAY_startDecode(io, DEF_MAX_VIDEO_FRAMES, THEORAPLAY_VIDFMT_RGBA);
|
||||||
|
if (!decoder) {
|
||||||
|
SDL_RWclose(&srcOps);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the decoder has parsed out some basic truths from the file.
|
||||||
|
while (!THEORAPLAY_isInitialized(decoder)) {
|
||||||
|
SDL_Delay(VIDEO_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once we're initialized, we can tell if this file has audio and/or video.
|
||||||
|
hasAudio = THEORAPLAY_hasAudioStream(decoder);
|
||||||
|
hasVideo = THEORAPLAY_hasVideoStream(decoder);
|
||||||
|
|
||||||
|
// Queue up the audio
|
||||||
|
if (hasAudio) {
|
||||||
|
while ((audio = THEORAPLAY_getAudio(decoder)) == NULL) {
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
SDL_Delay(VIDEO_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No video, so no point in doing anything else
|
||||||
|
if (!hasVideo) {
|
||||||
|
THEORAPLAY_stopDecode(decoder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until we have video
|
||||||
|
while ((video = THEORAPLAY_getVideo(decoder)) == NULL) {
|
||||||
|
SDL_Delay(VIDEO_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until we have audio, if applicable
|
||||||
|
audio = NULL;
|
||||||
|
if (hasAudio) {
|
||||||
|
while ((audio = THEORAPLAY_getAudio(decoder)) == NULL && THEORAPLAY_availableVideo(decoder) < DEF_MAX_VIDEO_FRAMES) {
|
||||||
|
SDL_Delay(VIDEO_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
movieAudioQueue = NULL;
|
||||||
|
movieAudioQueueTail = NULL;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void queueAudioPacket(const THEORAPLAY_AudioPacket *audio) {
|
||||||
|
AudioQueue *item = NULL;
|
||||||
|
|
||||||
|
if (!audio) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item = (AudioQueue *) malloc(sizeof (AudioQueue));
|
||||||
|
if (!item) {
|
||||||
|
THEORAPLAY_freeAudio(audio);
|
||||||
|
return; // oh well.
|
||||||
|
}
|
||||||
|
|
||||||
|
item->audio = audio;
|
||||||
|
item->offset = 0;
|
||||||
|
item->next = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
SDL_LockAudio();
|
||||||
|
if (movieAudioQueueTail) {
|
||||||
|
movieAudioQueueTail->next = item;
|
||||||
|
} else {
|
||||||
|
movieAudioQueue = item;
|
||||||
|
}
|
||||||
|
movieAudioQueueTail = item;
|
||||||
|
SDL_UnlockAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void queueMoreMovieAudio(THEORAPLAY_Decoder *decoder, const Uint32 now) {
|
||||||
|
const THEORAPLAY_AudioPacket *audio;
|
||||||
|
while ((audio = THEORAPLAY_getAudio(decoder)) != NULL) {
|
||||||
|
const unsigned int playms = audio->playms;
|
||||||
|
queueAudioPacket(audio);
|
||||||
|
if (playms >= now + 2000) { // don't let this get too far ahead.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL movieAudioCallback(void *userdata, uint8_t *stream, int len) {
|
||||||
|
// !!! FIXME: this should refuse to play if item->playms is in the future.
|
||||||
|
//const Uint32 now = SDL_GetTicks() - baseticks;
|
||||||
|
Sint16 *dst = (Sint16 *) stream;
|
||||||
|
|
||||||
|
while (movieAudioQueue && (len > 0)) {
|
||||||
|
volatile AudioQueue *item = movieAudioQueue;
|
||||||
|
AudioQueue *next = item->next;
|
||||||
|
const int channels = item->audio->channels;
|
||||||
|
|
||||||
|
const float *src = item->audio->samples + (item->offset * channels);
|
||||||
|
int cpy = (item->audio->frames - item->offset) * channels;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (cpy > (len / sizeof (Sint16))) {
|
||||||
|
cpy = len / sizeof (Sint16);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < cpy; i++) {
|
||||||
|
const float val = *(src++);
|
||||||
|
if (val < -1.0f) {
|
||||||
|
*(dst++) = -32768;
|
||||||
|
} else if (val > 1.0f) {
|
||||||
|
*(dst++) = 32767;
|
||||||
|
} else {
|
||||||
|
*(dst++) = (Sint16) (val * 32767.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item->offset += (cpy / channels);
|
||||||
|
len -= cpy * sizeof (Sint16);
|
||||||
|
|
||||||
|
if (item->offset >= item->audio->frames) {
|
||||||
|
THEORAPLAY_freeAudio(item->audio);
|
||||||
|
free((void *) item);
|
||||||
|
movieAudioQueue = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!movieAudioQueue) {
|
||||||
|
movieAudioQueueTail = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > 0) {
|
||||||
|
memset(dst, '\0', len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool startAudio()
|
||||||
|
{
|
||||||
|
SDL_AudioSpec spec;
|
||||||
|
memset(&spec, '\0', sizeof (SDL_AudioSpec));
|
||||||
|
spec.freq = audio->freq;
|
||||||
|
spec.format = AUDIO_S16SYS;
|
||||||
|
spec.channels = audio->channels;
|
||||||
|
spec.samples = 2048;
|
||||||
|
spec.callback = movieAudioCallback;
|
||||||
|
if (SDL_OpenAudio(&spec, NULL) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
queueAudioPacket(audio);
|
||||||
|
audio = NULL;
|
||||||
|
queueMoreMovieAudio(decoder, 0);
|
||||||
|
SDL_PauseAudio(0); // Start audio playback
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void play()
|
||||||
|
{
|
||||||
|
// Assuming every frame has the same duration.
|
||||||
|
Uint32 frameMs = (video->fps == 0.0) ? 0 : ((Uint32) (1000.0 / video->fps));
|
||||||
|
Uint32 baseTicks = SDL_GetTicks();
|
||||||
|
bool openedAudio = false;
|
||||||
|
while (THEORAPLAY_isDecoding(decoder)) {
|
||||||
|
// Check for reset/shutdown input
|
||||||
|
if(shState->graphics().updateMovieInput(this)) break;
|
||||||
|
|
||||||
|
const Uint32 now = SDL_GetTicks() - baseTicks;
|
||||||
|
|
||||||
|
if (!video) {
|
||||||
|
video = THEORAPLAY_getVideo(decoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAudio) {
|
||||||
|
if (!audio) {
|
||||||
|
audio = THEORAPLAY_getAudio(decoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio && !openedAudio) {
|
||||||
|
if(!startAudio()){
|
||||||
|
Debug() << "Error opening movie audio!";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
openedAudio = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video && (video->playms <= now)) {
|
||||||
|
if (frameMs && ((now - video->playms) >= frameMs)) {
|
||||||
|
// Skip frames to catch up, but keep track of the last one
|
||||||
|
// in case we catch up to a series of dupe frames, which
|
||||||
|
// means we'd have to draw that final frame and then wait for
|
||||||
|
// more.
|
||||||
|
const THEORAPLAY_VideoFrame *previous = video;
|
||||||
|
while ((video = THEORAPLAY_getVideo(decoder)) != NULL) {
|
||||||
|
THEORAPLAY_freeVideo(previous);
|
||||||
|
previous = video;
|
||||||
|
if ((now - video->playms) < frameMs) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!video) {
|
||||||
|
video = previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application is too far behind
|
||||||
|
if (!video) {
|
||||||
|
Debug() << "WARNING: Video playback cannot keep up!";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Got a video frame, now draw it
|
||||||
|
shState->graphics().drawMovieFrame(video, videoBitmap);
|
||||||
|
THEORAPLAY_freeVideo(video);
|
||||||
|
video = NULL;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Next video frame not yet ready, let the CPU breathe
|
||||||
|
SDL_Delay(VIDEO_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openedAudio) {
|
||||||
|
queueMoreMovieAudio(decoder, now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~Movie()
|
||||||
|
{
|
||||||
|
if (hasAudio) {
|
||||||
|
if (movieAudioQueueTail) {
|
||||||
|
THEORAPLAY_freeAudio(movieAudioQueueTail->audio);
|
||||||
|
}
|
||||||
|
movieAudioQueueTail = NULL;
|
||||||
|
|
||||||
|
if (movieAudioQueue) {
|
||||||
|
THEORAPLAY_freeAudio(movieAudioQueue->audio);
|
||||||
|
}
|
||||||
|
movieAudioQueue = NULL;
|
||||||
|
}
|
||||||
|
if (video) THEORAPLAY_freeVideo(video);
|
||||||
|
if (audio) THEORAPLAY_freeAudio(audio);
|
||||||
|
if (decoder) THEORAPLAY_stopDecode(decoder);
|
||||||
|
SDL_CloseAudio();
|
||||||
|
delete videoBitmap;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct MovieOpenHandler : FileSystem::OpenHandler
|
||||||
|
{
|
||||||
|
SDL_RWops *srcOps;
|
||||||
|
|
||||||
|
MovieOpenHandler(SDL_RWops &srcOps)
|
||||||
|
: srcOps(&srcOps)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool tryRead(SDL_RWops &ops, const char *ext)
|
||||||
|
{
|
||||||
|
*srcOps = ops;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct PingPong {
|
struct PingPong {
|
||||||
TEXFBO rt[2];
|
TEXFBO rt[2];
|
||||||
uint8_t srcInd, dstInd;
|
uint8_t srcInd, dstInd;
|
||||||
|
@ -925,322 +1237,59 @@ void Graphics::resizeScreen(int width, int height) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: I'd rather these not be declared in global space...
|
void Graphics::drawMovieFrame(const THEORAPLAY_VideoFrame* video, Bitmap *videoBitmap) {
|
||||||
static volatile AudioQueue *movieAudioQueue = NULL;
|
p->checkSyncLock();
|
||||||
static volatile AudioQueue *movieAudioQueueTail = NULL;
|
videoBitmap->replaceRaw(video->pixels, video->width * video->height * 4);
|
||||||
|
|
||||||
static void SDLCALL movieAudioCallback(void *userdata, uint8_t *stream, int len) {
|
shState->shaders().trans.bind();
|
||||||
// !!! FIXME: this should refuse to play if item->playms is in the future.
|
FBO::bind(p->screen.getPP().backBuffer().fbo);
|
||||||
//const Uint32 now = SDL_GetTicks() - baseticks;
|
FBO::clear();
|
||||||
Sint16 *dst = (Sint16 *) stream;
|
p->screenQuad.draw();
|
||||||
|
|
||||||
while (movieAudioQueue && (len > 0)) {
|
p->checkResize();
|
||||||
volatile AudioQueue *item = movieAudioQueue;
|
|
||||||
AudioQueue *next = item->next;
|
|
||||||
const int channels = item->audio->channels;
|
|
||||||
|
|
||||||
const float *src = item->audio->samples + (item->offset * channels);
|
/* Then blit it flipped and scaled to the screen */
|
||||||
int cpy = (item->audio->frames - item->offset) * channels;
|
FBO::unbind();
|
||||||
int i;
|
FBO::clear();
|
||||||
|
|
||||||
if (cpy > (len / sizeof (Sint16))) {
|
// Currently this stretches to fit the screen. VX Ace behavior is to center it and let the edges run off
|
||||||
cpy = len / sizeof (Sint16);
|
GLMeta::blitBeginScreen(Vec2i(p->winSize));
|
||||||
}
|
GLMeta::blitSource(p->screen.getPP().backBuffer());
|
||||||
|
p->metaBlitBufferFlippedScaled();
|
||||||
|
GLMeta::blitEnd();
|
||||||
|
|
||||||
for (i = 0; i < cpy; i++) {
|
p->swapGLBuffer();
|
||||||
const float val = *(src++);
|
|
||||||
if (val < -1.0f) {
|
|
||||||
*(dst++) = -32768;
|
|
||||||
} else if (val > 1.0f) {
|
|
||||||
*(dst++) = 32767;
|
|
||||||
} else {
|
|
||||||
*(dst++) = (Sint16) (val * 32767.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item->offset += (cpy / channels);
|
|
||||||
len -= cpy * sizeof (Sint16);
|
|
||||||
|
|
||||||
if (item->offset >= item->audio->frames) {
|
|
||||||
THEORAPLAY_freeAudio(item->audio);
|
|
||||||
free((void *) item);
|
|
||||||
movieAudioQueue = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!movieAudioQueue) {
|
|
||||||
movieAudioQueueTail = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len > 0) {
|
|
||||||
memset(dst, '\0', len);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void queueAudioPacket(const THEORAPLAY_AudioPacket *audio) {
|
bool Graphics::updateMovieInput(Movie *movie) {
|
||||||
AudioQueue *item = NULL;
|
return p->threadData->rqTerm || p->threadData->rqReset;
|
||||||
|
|
||||||
if (!audio) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
item = (AudioQueue *) malloc(sizeof (AudioQueue));
|
|
||||||
if (!item) {
|
|
||||||
THEORAPLAY_freeAudio(audio);
|
|
||||||
return; // oh well.
|
|
||||||
}
|
|
||||||
|
|
||||||
item->audio = audio;
|
|
||||||
item->offset = 0;
|
|
||||||
item->next = NULL;
|
|
||||||
|
|
||||||
|
|
||||||
SDL_LockAudio();
|
|
||||||
if (movieAudioQueueTail) {
|
|
||||||
movieAudioQueueTail->next = item;
|
|
||||||
} else {
|
|
||||||
movieAudioQueue = item;
|
|
||||||
}
|
|
||||||
movieAudioQueueTail = item;
|
|
||||||
SDL_UnlockAudio();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void queueMoreMovieAudio(THEORAPLAY_Decoder *decoder, const Uint32 now) {
|
|
||||||
const THEORAPLAY_AudioPacket *audio;
|
|
||||||
while ((audio = THEORAPLAY_getAudio(decoder)) != NULL) {
|
|
||||||
const unsigned int playms = audio->playms;
|
|
||||||
queueAudioPacket(audio);
|
|
||||||
if (playms >= now + 2000) { // don't let this get too far ahead.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Graphics::playMovie(const char *filename, int volume) {
|
void Graphics::playMovie(const char *filename, int volume) {
|
||||||
// TODO: mkxp somehow disregards file extensions when loading stuff. Use that instead of reinventing the wheel.
|
Movie *movie = new Movie();
|
||||||
const size_t bufferSize = 256;
|
MovieOpenHandler handler(movie->srcOps);
|
||||||
char filePath[bufferSize] = "";
|
shState->fileSystem().openRead(handler, filename);
|
||||||
snprintf(filePath, bufferSize, "%s.ogg", filename);
|
|
||||||
|
|
||||||
// Try adding the .ogg and .ogv extensions
|
if (movie->preparePlayback()) {
|
||||||
// TODO: Fails if the extension is already passed in
|
p->checkSyncLock();
|
||||||
if (!shState->fileSystem().exists(filePath)) {
|
p->screen.composite();
|
||||||
snprintf(filePath, bufferSize, "%s.ogg", filename);
|
|
||||||
}
|
movie->videoBitmap = new Bitmap(movie->video->width, movie->video->height);
|
||||||
if (!shState->fileSystem().exists(filePath)) {
|
TransShader &shader = shState->shaders().trans;
|
||||||
snprintf(filePath, bufferSize, "%s.ogv", filename);
|
shader.bind();
|
||||||
|
shader.applyViewportProj();
|
||||||
|
shader.setTransMap(movie->videoBitmap->getGLTypes().tex);
|
||||||
|
shader.setVague(256.0f);
|
||||||
|
shader.setTexSize(p->scRes);
|
||||||
|
glState.blend.pushSet(false);
|
||||||
|
|
||||||
|
movie->play();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shState->fileSystem().exists(filePath)) {
|
|
||||||
// Movie file not found
|
|
||||||
Debug() << "Unable to open movie file: " << filePath;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Get the actual format first... somehow. Seek the header?
|
|
||||||
// Note: VX Ace itself displays odd colors for yuv422p format
|
|
||||||
// theoraplay assets that it must use TH_PF_420. Will have to dip into that.
|
|
||||||
// Leaving this as VIDFMT_RGBA may be fine. Maybe.
|
|
||||||
// https://theora.org/doc/libtheora-1.0/codec_8h.html
|
|
||||||
// https://ffmpeg.org/doxygen/0.11/group__lavc__misc__pixfmt.html
|
|
||||||
THEORAPLAY_Decoder *decoder = THEORAPLAY_startDecodeFile(filePath, DEF_MAX_VIDEO_FRAMES, THEORAPLAY_VIDFMT_RGBA);
|
|
||||||
if (!decoder) {
|
|
||||||
Debug() << "Failed to start decoding movie file: " << filePath;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until the decoder has parsed out some basic truths from the file.
|
|
||||||
while (!THEORAPLAY_isInitialized(decoder)) {
|
|
||||||
SDL_Delay(VIDEO_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once we're initialized, we can tell if this file has audio and/or video.
|
|
||||||
bool hasAudio = THEORAPLAY_hasAudioStream(decoder);
|
|
||||||
bool hasVideo = THEORAPLAY_hasVideoStream(decoder);
|
|
||||||
|
|
||||||
// No video, so no point in doing anything else
|
|
||||||
if (!hasVideo) {
|
|
||||||
THEORAPLAY_stopDecode(decoder);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until we have video
|
|
||||||
const THEORAPLAY_VideoFrame *video = NULL;
|
|
||||||
while ((video = THEORAPLAY_getVideo(decoder)) == NULL) {
|
|
||||||
SDL_Delay(VIDEO_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until we have audio, if applicable
|
|
||||||
const THEORAPLAY_AudioPacket *audio = NULL;
|
|
||||||
if (hasAudio) {
|
|
||||||
while ((audio = THEORAPLAY_getAudio(decoder)) == NULL && THEORAPLAY_availableVideo(decoder) < DEF_MAX_VIDEO_FRAMES) {
|
|
||||||
SDL_Delay(VIDEO_DELAY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Capture new scene */
|
|
||||||
p->checkSyncLock();
|
|
||||||
p->screen.composite();
|
|
||||||
TEXFBO &videoBuffer = p->screen.getPP().backBuffer();
|
|
||||||
|
|
||||||
Bitmap *videoBitmap = new Bitmap(video->width, video->height);
|
|
||||||
TransShader &shader = shState->shaders().trans;
|
|
||||||
shader.bind();
|
|
||||||
shader.applyViewportProj();
|
|
||||||
shader.setTransMap(videoBitmap->getGLTypes().tex);
|
|
||||||
shader.setVague(256.0f);
|
|
||||||
shader.setTexSize(p->scRes);
|
|
||||||
glState.blend.pushSet(false);
|
|
||||||
|
|
||||||
// Queue up the audio
|
|
||||||
if (hasAudio) {
|
|
||||||
// shState->audio().moviePlay(filePath, volume, 100);
|
|
||||||
while ((audio = THEORAPLAY_getAudio(decoder)) == NULL) {
|
|
||||||
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.
|
|
||||||
}
|
|
||||||
SDL_Delay(VIDEO_DELAY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flags on what to do upon playback end
|
|
||||||
bool shutDown = false;
|
|
||||||
bool scriptReset = false;
|
|
||||||
|
|
||||||
// Assuming every frame has the same duration.
|
|
||||||
Uint32 frameMs = (video->fps == 0.0) ? 0 : ((Uint32) (1000.0 / video->fps));
|
|
||||||
Uint32 baseTicks = SDL_GetTicks();
|
|
||||||
bool openedAudio = false;
|
|
||||||
// TODO: There is now an issue with the shutdown/app close where it is not immediate. It is
|
|
||||||
// hanging somewhere while audio is processing
|
|
||||||
while (THEORAPLAY_isDecoding(decoder)) {
|
|
||||||
if (p->threadData->rqTerm) {
|
|
||||||
shutDown = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p->threadData->rqReset) {
|
|
||||||
scriptReset = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Uint32 now = SDL_GetTicks() - baseTicks;
|
|
||||||
|
|
||||||
if (!video) {
|
|
||||||
video = THEORAPLAY_getVideo(decoder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAudio) {
|
|
||||||
if (!audio) {
|
|
||||||
audio = THEORAPLAY_getAudio(decoder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audio && !openedAudio) {
|
|
||||||
SDL_AudioSpec spec;
|
|
||||||
memset(&spec, '\0', sizeof (SDL_AudioSpec));
|
|
||||||
spec.freq = audio->freq;
|
|
||||||
spec.format = AUDIO_S16SYS;
|
|
||||||
spec.channels = audio->channels;
|
|
||||||
spec.samples = 2048;
|
|
||||||
spec.callback = movieAudioCallback;
|
|
||||||
if (SDL_OpenAudio(&spec, NULL) != 0) {
|
|
||||||
Debug() << "Error opening movie audio!";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
openedAudio = true;
|
|
||||||
queueAudioPacket(audio);
|
|
||||||
audio = NULL;
|
|
||||||
queueMoreMovieAudio(decoder, 0);
|
|
||||||
SDL_PauseAudio(0); // Start audio playback
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (video && (video->playms <= now)) {
|
|
||||||
if (frameMs && ((now - video->playms) >= frameMs)) {
|
|
||||||
// Skip frames to catch up, but keep track of the last one
|
|
||||||
// in case we catch up to a series of dupe frames, which
|
|
||||||
// means we'd have to draw that final frame and then wait for
|
|
||||||
// more.
|
|
||||||
const THEORAPLAY_VideoFrame *previous = video;
|
|
||||||
while ((video = THEORAPLAY_getVideo(decoder)) != NULL) {
|
|
||||||
THEORAPLAY_freeVideo(previous);
|
|
||||||
previous = video;
|
|
||||||
if ((now - video->playms) < frameMs) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!video) {
|
|
||||||
video = previous;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Application is too far behind
|
|
||||||
if (!video) {
|
|
||||||
Debug() << "WARNING: Video playback cannot keep up!";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Got a video frame, now draw it
|
|
||||||
p->checkSyncLock();
|
|
||||||
videoBitmap->replaceRaw(video->pixels, video->width * video->height * 4);
|
|
||||||
|
|
||||||
shader.bind();
|
|
||||||
FBO::bind(videoBuffer.fbo);
|
|
||||||
FBO::clear();
|
|
||||||
p->screenQuad.draw();
|
|
||||||
|
|
||||||
p->checkResize();
|
|
||||||
|
|
||||||
/* Then blit it flipped and scaled to the screen */
|
|
||||||
FBO::unbind();
|
|
||||||
FBO::clear();
|
|
||||||
|
|
||||||
// Currently this stretches to fit the screen. VX Ace behavior is to center it and let the edges run off
|
|
||||||
GLMeta::blitBeginScreen(Vec2i(p->winSize));
|
|
||||||
GLMeta::blitSource(videoBuffer);
|
|
||||||
p->metaBlitBufferFlippedScaled();
|
|
||||||
GLMeta::blitEnd();
|
|
||||||
|
|
||||||
p->swapGLBuffer();
|
|
||||||
|
|
||||||
|
|
||||||
THEORAPLAY_freeVideo(video);
|
|
||||||
video = NULL;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Next video frame not yet ready, let the CPU breathe
|
|
||||||
SDL_Delay(VIDEO_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (openedAudio) {
|
|
||||||
queueMoreMovieAudio(decoder, now);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAudio) {
|
|
||||||
if (movieAudioQueueTail) {
|
|
||||||
THEORAPLAY_freeAudio(movieAudioQueueTail->audio);
|
|
||||||
}
|
|
||||||
movieAudioQueueTail = NULL;
|
|
||||||
|
|
||||||
if (movieAudioQueue) {
|
|
||||||
THEORAPLAY_freeAudio(movieAudioQueue->audio);
|
|
||||||
}
|
|
||||||
movieAudioQueue = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (video) THEORAPLAY_freeVideo(video);
|
|
||||||
if (audio) THEORAPLAY_freeAudio(audio);
|
|
||||||
if (decoder) THEORAPLAY_stopDecode(decoder);
|
|
||||||
glState.blend.pop();
|
glState.blend.pop();
|
||||||
videoBitmap->dispose();
|
delete movie;
|
||||||
delete videoBitmap;
|
if(p->threadData->rqReset) scriptBinding->reset();
|
||||||
SDL_CloseAudio();
|
if(p->threadData->rqTerm) p->shutdown();
|
||||||
if(scriptReset) scriptBinding->reset();
|
|
||||||
if(shutDown) p->shutdown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Graphics::screenshot(const char *filename) {
|
void Graphics::screenshot(const char *filename) {
|
||||||
|
|
|
@ -30,6 +30,8 @@ class Disposable;
|
||||||
struct RGSSThreadData;
|
struct RGSSThreadData;
|
||||||
struct GraphicsPrivate;
|
struct GraphicsPrivate;
|
||||||
struct AtomicFlag;
|
struct AtomicFlag;
|
||||||
|
struct THEORAPLAY_VideoFrame;
|
||||||
|
struct Movie;
|
||||||
|
|
||||||
class Graphics
|
class Graphics
|
||||||
{
|
{
|
||||||
|
@ -57,6 +59,8 @@ public:
|
||||||
int width() const;
|
int width() const;
|
||||||
int height() const;
|
int height() const;
|
||||||
void resizeScreen(int width, int height);
|
void resizeScreen(int width, int height);
|
||||||
|
void drawMovieFrame(const THEORAPLAY_VideoFrame* video, Bitmap *videoBitmap);
|
||||||
|
bool updateMovieInput(Movie *movie);
|
||||||
void playMovie(const char *filename, int volume);
|
void playMovie(const char *filename, int volume);
|
||||||
void screenshot(const char *filename);
|
void screenshot(const char *filename);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue