Implement DrawMovie, DrawMovie2 and PlayMovie HLLs

These HLLs play .mpg movie files (later games use .alm file extension,
but the format is the same, i.e. MPEG-PS) that contain MPEG1 video and
MP2 audio streams.

pl_mpeg.h is the PL_MPEG library (https://github.com/phoboslab/pl_mpeg)
imported from commit 5aeef4d07593be1960a0e4c7fe204809cfae30bd.

This adds a few mixer functions (mixer_stream_*) for playing custom
audio streams under the master mixer.
This commit is contained in:
kichikuou
2023-01-06 12:12:07 +09:00
parent b6f9999a56
commit de2db1af93
11 changed files with 4763 additions and 71 deletions
+5
View File
@@ -49,6 +49,11 @@ int mixer_set_volume(int n, int volume);
int mixer_get_mute(int n, int *mute);
int mixer_set_mute(int n, int mute);
struct sts_mixer_stream_t;
int mixer_stream_play(struct sts_mixer_stream_t* stream, int volume);
bool mixer_stream_set_volume(int voice, int volume);
void mixer_stream_stop(int voice);
struct archive_data;
struct channel;
enum asset_type;
+31
View File
@@ -0,0 +1,31 @@
/* Copyright (C) 2023 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#ifndef SYSTEM4_MOVIE_H
#define SYSTEM4_MOVIE_H
struct movie_context;
struct sact_sprite;
struct movie_context *movie_load(const char *filename);
void movie_free(struct movie_context *mc);
bool movie_play(struct movie_context *mc);
bool movie_draw(struct movie_context *mc, struct sact_sprite *sprite);
bool movie_is_end(struct movie_context *mc);
int movie_get_position(struct movie_context *mc);
bool movie_set_volume(struct movie_context *mc, int volume /* 0-100 */);
#endif /* SYSTEM4_MOVIE_H */
+4265
View File
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -39,6 +39,11 @@ enum {
STS_MIXER_SAMPLE_FORMAT_FLOAT // floats
};
enum {
STS_STREAM_COMPLETE = 0,
STS_STREAM_CONTINUE = 1
};
////////////////////////////////////////////////////////////////////////////////
//
@@ -66,7 +71,7 @@ typedef struct {
// The callback which will be called when the stream needs more data.
typedef int (*sts_mixer_stream_callback)(sts_mixer_sample_t* sample, void* userdata);
typedef struct {
typedef struct sts_mixer_stream_t {
void* userdata; // a userdata pointer which will passed to the callback
sts_mixer_stream_callback callback; // this callback will be called when the stream needs more data
sts_mixer_sample_t sample; // the current stream "sample" which holds the current piece of audio
@@ -160,11 +165,6 @@ enum {
STS_MIXER_VOICE_STREAMING
};
enum {
STS_STREAM_COMPLETE = 0,
STS_STREAM_CONTINUE = 1
};
static float sts_mixer__clamp(const float value, const float min, const float max) {
if (value < min) return min;
else if (value > max) return max;
@@ -329,14 +329,14 @@ void sts_mixer_mix_audio(sts_mixer_t* mixer, void* output, unsigned int samples)
voice->position = 0.0f;
position = 0;
}
left += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position) * voice->gain);
right += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position + 1) * voice->gain);
// added in xsystem4: allow stopping stream via callback return value
if (status == STS_STREAM_COMPLETE) {
sts_mixer_stop_voice(mixer, i);
} else {
left += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position) * voice->gain);
right += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position + 1) * voice->gain);
voice->position += (float)voice->stream->sample.frequency * advance;
}
}
}
}
+37
View File
@@ -0,0 +1,37 @@
/* Copyright (C) 2023 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
uniform sampler2D texture_y;
uniform sampler2D texture_cb;
uniform sampler2D texture_cr;
in vec2 tex_coord;
out vec4 frag_color;
mat4 rec601 = mat4(
1.16438, 0.00000, 1.59603, -0.87079,
1.16438, -0.39176, -0.81297, 0.52959,
1.16438, 2.01723, 0.00000, -1.08139,
0.0, 0.0, 0.0, 1.0
);
void main() {
float y = texture(texture_y, tex_coord).r;
float cb = texture(texture_cb, tex_coord).r;
float cr = texture(texture_cr, tex_coord).r;
frag_color = vec4(y, cb, cr, 1.0) * rec601;
}
+26
View File
@@ -717,3 +717,29 @@ int mixer_set_mute(int n, int mute)
mixers[n].muted = !!mute;
return 1;
}
int mixer_stream_play(sts_mixer_stream_t* stream, int volume)
{
SDL_LockAudioDevice(audio_device);
float gain = clamp(0.0f, 1.0f, (float)volume / 100.0f);
int voice = sts_mixer_play_stream(&master->mixer, stream, gain);
SDL_UnlockAudioDevice(audio_device);
return voice;
}
bool mixer_stream_set_volume(int voice, int volume)
{
if (voice < 0 || voice >= STS_MIXER_VOICES)
return false;
SDL_LockAudioDevice(audio_device);
master->mixer.voices[voice].gain = clamp(0.0f, 1.0f, (float)volume / 100.0f);
SDL_UnlockAudioDevice(audio_device);
return true;
}
void mixer_stream_stop(int voice)
{
SDL_LockAudioDevice(audio_device);
sts_mixer_stop_voice(&master->mixer, voice);
SDL_UnlockAudioDevice(audio_device);
}
+76 -13
View File
@@ -14,22 +14,85 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include "hll.h"
#include "system4/string.h"
HLL_WARN_UNIMPLEMENTED( , void, DrawMovie, Release);
HLL_WARN_UNIMPLEMENTED(0, bool, DrawMovie, Load, possibly_unused struct string *filename);
//bool DrawMovie_Run(void);
//bool DrawMovie_Draw(int nSprite);
//bool DrawMovie_SetVolume(int nVolume);
//bool DrawMovie_IsEnd(void);
//int DrawMovie_GetCount(void);
#include "hll.h"
#include "movie.h"
#include "sact.h"
static struct movie_context *mc;
static void DrawMovie_Release(void)
{
if (mc)
movie_free(mc);
mc = NULL;
}
static bool DrawMovie_Load(struct string *filename)
{
if (mc)
movie_free(mc);
mc = movie_load(filename->text);
return !!mc;
}
static bool DrawMovie_Run(void)
{
if (!mc)
return false;
return movie_play(mc);
}
static bool DrawMovie_Draw(int sprite)
{
if (!mc)
return false;
struct sact_sprite *sp = sact_try_get_sprite(sprite);
if (!sp)
return false;
return movie_draw(mc, sp);
}
static bool DrawMovie_SetVolume(int volume)
{
if (!mc)
return false;
return movie_set_volume(mc, volume);
}
static bool DrawMovie_IsEnd(void)
{
if (!mc)
return true;
return movie_is_end(mc);
}
static int DrawMovie_GetCount(void)
{
if (!mc)
return 0;
return movie_get_position(mc);
}
HLL_LIBRARY(DrawMovie,
HLL_EXPORT(Release, DrawMovie_Release),
HLL_EXPORT(Load, DrawMovie_Load),
HLL_TODO_EXPORT(Run, DrawMovie_Run),
HLL_TODO_EXPORT(Draw, DrawMovie_Draw),
HLL_TODO_EXPORT(SetVolume, DrawMovie_SetVolume),
HLL_TODO_EXPORT(IsEnd, DrawMovie_IsEnd),
HLL_TODO_EXPORT(GetCount, DrawMovie_GetCount)
HLL_EXPORT(Run, DrawMovie_Run),
HLL_EXPORT(Draw, DrawMovie_Draw),
HLL_EXPORT(SetVolume, DrawMovie_SetVolume),
HLL_EXPORT(IsEnd, DrawMovie_IsEnd),
HLL_EXPORT(GetCount, DrawMovie_GetCount)
);
HLL_LIBRARY(DrawMovie2,
HLL_EXPORT(Release, DrawMovie_Release),
HLL_EXPORT(Load, DrawMovie_Load),
HLL_EXPORT(Run, DrawMovie_Run),
HLL_EXPORT(Draw, DrawMovie_Draw),
HLL_EXPORT(SetVolume, DrawMovie_SetVolume),
HLL_EXPORT(IsEnd, DrawMovie_IsEnd),
HLL_EXPORT(GetCount, DrawMovie_GetCount)
);
-35
View File
@@ -1,35 +0,0 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include "hll.h"
HLL_WARN_UNIMPLEMENTED( , void, DrawMovie2, Release);
HLL_WARN_UNIMPLEMENTED(0, bool, DrawMovie2, Load, possibly_unused struct string *filename);
//bool DrawMovie2_Run(void);
//bool DrawMovie2_Draw(int nSprite);
//bool DrawMovie2_SetVolume(int nVolume);
//bool DrawMovie2_IsEnd(void);
//int DrawMovie2_GetCount(void);
HLL_LIBRARY(DrawMovie2,
HLL_EXPORT(Release, DrawMovie2_Release),
HLL_EXPORT(Load, DrawMovie2_Load),
HLL_TODO_EXPORT(Run, DrawMovie2_Run),
HLL_TODO_EXPORT(Draw, DrawMovie2_Draw),
HLL_TODO_EXPORT(SetVolume, DrawMovie2_SetVolume),
HLL_TODO_EXPORT(IsEnd, DrawMovie2_IsEnd),
HLL_TODO_EXPORT(GetCount, DrawMovie2_GetCount)
);
+60 -13
View File
@@ -14,20 +14,67 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include "hll.h"
#include "system4/string.h"
HLL_WARN_UNIMPLEMENTED(0, int, PlayMovie, Init, void);
//int PlayMovie_Load(struct string *filename);
//int PlayMovie_Play(void);
//void PlayMovie_Stop(void);
//int PlayMovie_IsPlay(void);
//void PlayMovie_Release(void);
#include "hll.h"
#include "movie.h"
#include "gfx/gfx.h"
static struct movie_context *mc;
static int PlayMovie_Init(void)
{
return 1;
}
static int PlayMovie_Load(struct string *filename)
{
if (mc)
movie_free(mc);
mc = movie_load(filename->text);
return !!mc;
}
static int PlayMovie_Play(void)
{
if (!mc)
return false;
return movie_play(mc);
}
HLL_WARN_UNIMPLEMENTED(, void, PlayMovie, Stop, void);
// PlayMovie is used like this:
//
// PlayMovie.Load(filename);
// PlayMovie.Play();
// while (PlayMovie.IsPlay()) {
// system.Peek();
// system.Sleep(1);
// }
//
// SACT.Update is not called inside the loop, so we do rendering in IsPlay().
static int PlayMovie_IsPlay(void)
{
if (!mc)
return false;
if (!movie_draw(mc, NULL))
return false;
return !movie_is_end(mc);
}
static void PlayMovie_Release(void)
{
if (mc)
movie_free(mc);
mc = NULL;
}
HLL_LIBRARY(PlayMovie,
HLL_EXPORT(Init, PlayMovie_Init)
//HLL_EXPORT(Load, PlayMovie_Load),
//HLL_EXPORT(Play, PlayMovie_Play),
//HLL_EXPORT(Stop, PlayMovie_Stop),
//HLL_EXPORT(IsPlay, PlayMovie_IsPlay),
//HLL_EXPORT(Release, PlayMovie_Release)
HLL_EXPORT(Init, PlayMovie_Init),
HLL_EXPORT(Load, PlayMovie_Load),
HLL_EXPORT(Play, PlayMovie_Play),
HLL_EXPORT(Stop, PlayMovie_Stop),
HLL_EXPORT(IsPlay, PlayMovie_IsPlay),
HLL_EXPORT(Release, PlayMovie_Release)
);
+1 -1
View File
@@ -20,6 +20,7 @@ xsystem4 = [version_h,
'heap.c',
'id_pool.c',
'input.c',
'movie.c',
'page.c',
'resume.c',
'savedata.c',
@@ -80,7 +81,6 @@ xsystem4 = [version_h,
'hll/DrawDungeon.c',
'hll/DrawGraph.c',
'hll/DrawMovie.c',
'hll/DrawMovie2.c',
'hll/DrawPluginManager.c',
'hll/DrawSimpleText.c',
'hll/File.c',
+253
View File
@@ -0,0 +1,253 @@
/* Copyright (C) 2023 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdbool.h>
#include <SDL.h>
#include "system4.h"
#include "gfx/gfx.h"
#include "movie.h"
#include "mixer.h"
#include "sprite.h"
#include "sts_mixer.h"
#include "xsystem4.h"
#define PL_MPEG_IMPLEMENTATION
#include "pl_mpeg.h"
static Shader movie_shader;
struct movie_context {
plm_t *plm;
SDL_mutex *decoder_mutex;
plm_frame_t *pending_video_frame;
GLuint textures[3]; // Y, Cb, Cr
sts_mixer_stream_t sts_stream;
int voice;
int volume;
// Time keeping data. Written by audio handler and referenced by video
// handler (i.e. we sync the video to the audio).
double stream_time; // in seconds
uint32_t wall_time_ms;
SDL_mutex *timer_mutex;
};
static int audio_callback(sts_mixer_sample_t *sample, void *data)
{
struct movie_context *mc = data;
assert(sample == &mc->sts_stream.sample);
SDL_LockMutex(mc->decoder_mutex);
plm_samples_t *frame = plm_decode_audio(mc->plm);
SDL_UnlockMutex(mc->decoder_mutex);
if (!frame) {
sample->length = 0;
sample->data = NULL;
mc->voice = -1;
return STS_STREAM_COMPLETE;
}
sample->length = frame->count * 2;
sample->data = frame->interleaved;
// Update the timestamp.
SDL_LockMutex(mc->timer_mutex);
mc->stream_time = frame->time;
mc->wall_time_ms = SDL_GetTicks();
SDL_UnlockMutex(mc->timer_mutex);
return STS_STREAM_CONTINUE;
}
static void prepare_movie_shader(struct gfx_render_job *job, void *data)
{
struct movie_context *mc = data;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mc->textures[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, mc->textures[1]);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, mc->textures[2]);
}
static void load_movie_shader()
{
gfx_load_shader(&movie_shader, "shaders/render.v.glsl", "shaders/movie.f.glsl");
glUseProgram(movie_shader.program);
glUniform1i(glGetUniformLocation(movie_shader.program, "texture_y"), 0);
glUniform1i(glGetUniformLocation(movie_shader.program, "texture_cb"), 1);
glUniform1i(glGetUniformLocation(movie_shader.program, "texture_cr"), 2);
movie_shader.prepare = prepare_movie_shader;
}
static void update_texture(GLuint unit, GLuint texture, plm_plane_t *plane)
{
glActiveTexture(unit);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, plane->width, plane->height, 0,
GL_RED, GL_UNSIGNED_BYTE, plane->data);
}
struct movie_context *movie_load(const char *filename)
{
struct movie_context *mc = xcalloc(1, sizeof(struct movie_context));
char *path = gamedir_path(filename);
mc->plm = plm_create_with_filename(path);
if (!mc->plm) {
WARNING("Couldn't open %s", path);
free(path);
movie_free(mc);
return NULL;
}
free(path);
if (!movie_shader.program)
load_movie_shader();
glGenTextures(3, mc->textures);
for (int i = 0; i < 3; i++) {
glBindTexture(GL_TEXTURE_2D, mc->textures[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
mc->decoder_mutex = SDL_CreateMutex();
mc->timer_mutex = SDL_CreateMutex();
mc->voice = -1;
mc->volume = 100;
return mc;
}
void movie_free(struct movie_context *mc)
{
if (mc->voice >= 0)
mixer_stream_stop(mc->voice);
if (mc->plm)
plm_destroy(mc->plm);
if (mc->textures[0])
glDeleteTextures(3, mc->textures);
if (mc->decoder_mutex)
SDL_DestroyMutex(mc->decoder_mutex);
if (mc->timer_mutex)
SDL_DestroyMutex(mc->timer_mutex);
free(mc);
}
bool movie_play(struct movie_context *mc)
{
// Start the audio stream.
mc->stream_time = 0.0;
mc->wall_time_ms = SDL_GetTicks();
mc->sts_stream.userdata = mc;
mc->sts_stream.callback = audio_callback;
mc->sts_stream.sample.frequency = plm_get_samplerate(mc->plm);
mc->sts_stream.sample.audio_format = STS_MIXER_SAMPLE_FORMAT_FLOAT;
mc->voice = mixer_stream_play(&mc->sts_stream, mc->volume);
return true;
}
bool movie_draw(struct movie_context *mc, struct sact_sprite *sprite)
{
// Decode a frame, unless we already have one.
plm_frame_t *frame = mc->pending_video_frame;
mc->pending_video_frame = NULL;
if (!frame) {
SDL_LockMutex(mc->decoder_mutex);
frame = plm_decode_video(mc->plm);
SDL_UnlockMutex(mc->decoder_mutex);
if (!frame)
return plm_video_has_ended(mc->plm->video_decoder);
}
// If the frame's timestamp is in the future, save the frame and return.
SDL_LockMutex(mc->timer_mutex);
double now = mc->stream_time + (SDL_GetTicks() - mc->wall_time_ms) / 1000.0;
SDL_UnlockMutex(mc->timer_mutex);
if (frame->time > now) {
mc->pending_video_frame = frame;
return true;
}
// Render the frame.
update_texture(GL_TEXTURE0, mc->textures[0], &frame->y);
update_texture(GL_TEXTURE1, mc->textures[1], &frame->cb);
update_texture(GL_TEXTURE2, mc->textures[2], &frame->cr);
float w, h;
GLuint fbo;
if (sprite) {
struct texture *texture = sprite_get_texture(sprite);
w = texture->w;
h = texture->h;
fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, texture, 0, 0, w, h);
} else {
// Draw directly to the main framebuffer.
w = config.view_width;
h = config.view_height;
gfx_clear();
}
mat4 world_transform = MAT4(
w, 0, 0, 0,
0, h, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
mat4 wv_transform = WV_TRANSFORM(w, h);
struct gfx_render_job job = {
.shader = &movie_shader,
.world_transform = world_transform[0],
.view_transform = wv_transform[0],
.data = mc
};
gfx_render(&job);
if (sprite) {
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
sprite_dirty(sprite);
} else {
gfx_swap();
}
return true;
}
bool movie_is_end(struct movie_context *mc)
{
return plm_has_ended(mc->plm);
}
int movie_get_position(struct movie_context *mc)
{
SDL_LockMutex(mc->timer_mutex);
int ms = mc->wall_time_ms ? mc->stream_time * 1000 + SDL_GetTicks() - mc->wall_time_ms : 0;
SDL_UnlockMutex(mc->timer_mutex);
return ms;
}
bool movie_set_volume(struct movie_context *mc, int volume)
{
mc->volume = volume;
if (mc->voice >= 0)
mixer_stream_set_volume(mc->voice, volume);
return true;
}