mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-23 16:00:32 +02:00
Android: fix audio, which never played; VR: wider look-at tests
- SDL 3.4 holds an audio stream's lock through its callback, and the game's data was put into the stream from the thread that runs the game's callback, which then waited for that lock for ever: the first callback never returned and nothing was heard. The data is now gathered and put in by SDL's own thread once the game is done. - debug.audio_log logs the level of the mixed sound. - In VR a player's unit sees what its head points at within 15 degrees, or what the weapon's laser points at, since the head only points roughly where the eyes look (the training's flashing lights ask for 5 degrees). - vr.log_performance also logs the unit's looking vector. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bdd6ad3fc8
commit
fff0df8d86
7 files changed
+148
-2
No files matched your search
@@ -239,10 +239,20 @@ int host_sdl_rumble_gamepad(uint32_t gamepad, uint32_t low, uint32_t high, uint3
|
||||
|
||||
/* SDL calls audio_callback on its own audio thread, which cannot run guest
|
||||
code; it passes each request to the stream's thread (audio_thread), which
|
||||
can, and waits for it to be done */
|
||||
can, and waits for it to be done. SDL holds the stream's lock through the
|
||||
callback, so the data the game puts into the stream meanwhile, from the
|
||||
other thread, is gathered here and put in by SDL's thread once the game is
|
||||
done (SDL_PutAudioStreamData from audio_thread would wait for that lock
|
||||
for ever, and the game would never be heard). */
|
||||
struct audio_binding
|
||||
{
|
||||
uint32_t handle;
|
||||
SDL_AudioStream *stream;
|
||||
pthread_t thread;
|
||||
int thread_known;
|
||||
unsigned char *gathered;
|
||||
size_t gathered_size;
|
||||
size_t gathered_capacity;
|
||||
uint32_t callback;
|
||||
uint32_t userdata;
|
||||
pthread_mutex_t lock;
|
||||
@@ -258,6 +268,8 @@ static void *audio_thread(void *context)
|
||||
struct audio_binding *binding = context;
|
||||
|
||||
pthread_mutex_lock(&binding->lock);
|
||||
binding->thread = pthread_self();
|
||||
binding->thread_known = 1;
|
||||
for (;;)
|
||||
{
|
||||
int additional, total;
|
||||
@@ -287,9 +299,18 @@ static void SDLCALL audio_callback(void *userdata, SDL_AudioStream *stream, int
|
||||
pthread_cond_signal(&binding->requested);
|
||||
while (binding->pending)
|
||||
pthread_cond_wait(&binding->done, &binding->lock);
|
||||
if (binding->gathered_size)
|
||||
{
|
||||
SDL_PutAudioStreamData(stream, binding->gathered, (int)binding->gathered_size);
|
||||
binding->gathered_size = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&binding->lock);
|
||||
}
|
||||
|
||||
#define AUDIO_BINDINGS 4
|
||||
|
||||
static struct audio_binding *audio_bindings[AUDIO_BINDINGS];
|
||||
|
||||
uint32_t host_sdl_open_audio_stream(uint32_t device, const void *spec, uint32_t callback, uint32_t userdata)
|
||||
{
|
||||
struct audio_binding *binding = SDL_calloc(1, sizeof(*binding));
|
||||
@@ -309,16 +330,55 @@ uint32_t host_sdl_open_audio_stream(uint32_t device, const void *spec, uint32_t
|
||||
}
|
||||
/* the device starts paused, so no callback can run before this */
|
||||
binding->handle = handle_new(_handle_audio, stream);
|
||||
binding->stream = stream;
|
||||
if (callback && host_native_thread_create(audio_thread, binding, 256 * 1024) != 0)
|
||||
host_fatal("cannot start the audio thread");
|
||||
{
|
||||
int index;
|
||||
|
||||
for (index = 0; index < AUDIO_BINDINGS; index++)
|
||||
{
|
||||
if (!audio_bindings[index])
|
||||
{
|
||||
audio_bindings[index] = binding;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return binding->handle;
|
||||
}
|
||||
|
||||
int host_sdl_put_audio_stream_data(uint32_t stream, const void *data, int length)
|
||||
{
|
||||
SDL_AudioStream *object = handle_get(stream, _handle_audio);
|
||||
int index;
|
||||
|
||||
return object ? SDL_PutAudioStreamData(object, data, length) : 0;
|
||||
if (!object || length < 0)
|
||||
return 0;
|
||||
/* from inside the stream's callback: gathered (struct audio_binding) */
|
||||
for (index = 0; index < AUDIO_BINDINGS; index++)
|
||||
{
|
||||
struct audio_binding *binding = audio_bindings[index];
|
||||
|
||||
if (binding && binding->stream == object && binding->thread_known &&
|
||||
pthread_equal(binding->thread, pthread_self()))
|
||||
{
|
||||
if (binding->gathered_size + (size_t)length > binding->gathered_capacity)
|
||||
{
|
||||
size_t capacity = (binding->gathered_size + (size_t)length) * 2;
|
||||
unsigned char *grown = SDL_realloc(binding->gathered, capacity);
|
||||
|
||||
if (!grown)
|
||||
return 0;
|
||||
binding->gathered = grown;
|
||||
binding->gathered_capacity = capacity;
|
||||
}
|
||||
memcpy(binding->gathered + binding->gathered_size, data, (size_t)length);
|
||||
binding->gathered_size += (size_t)length;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return SDL_PutAudioStreamData(object, data, length);
|
||||
}
|
||||
|
||||
int host_sdl_resume_audio_stream_device(uint32_t stream)
|
||||
|
||||
@@ -62,6 +62,10 @@ int halo_vr_aim(short local_player_index, float current_yaw, float current_pitch
|
||||
/* where the head looks, in the game's axes; 0 when not in VR play */
|
||||
int halo_vr_look_vector(short local_player_index, float vector[3]);
|
||||
|
||||
/* vr.log_performance: logs the unit's looking vector against the head's
|
||||
every second (players.c) */
|
||||
void halo_vr_debug_look(short local_player_index, const float unit_looking[3], int in_vehicle);
|
||||
|
||||
/* how far the view has turned since this was last asked, once past a few
|
||||
degrees on an axis (the other axis 0); nonzero if it has. The game counts
|
||||
it as looking around (player_control.c's action tests). */
|
||||
|
||||
@@ -429,6 +429,33 @@ static void mix(float *output, unsigned long frames)
|
||||
|
||||
static SDL_AudioStream *audio_stream;
|
||||
static BOOL audio_started = FALSE;
|
||||
static BOOL audio_log = FALSE;
|
||||
|
||||
/* debug.audio_log: the level of what is mixed, every five seconds */
|
||||
static void audio_log_level(const float *samples, unsigned long count)
|
||||
{
|
||||
static double sum;
|
||||
static float peak;
|
||||
static unsigned long total;
|
||||
unsigned long index;
|
||||
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
float value = samples[index] < 0.0f ? -samples[index] : samples[index];
|
||||
|
||||
sum += (double)value * value;
|
||||
if (value > peak)
|
||||
peak = value;
|
||||
}
|
||||
total += count;
|
||||
if (total >= (unsigned long)OUTPUT_RATE * OUTPUT_CHANNELS * 5)
|
||||
{
|
||||
platform_log("audio: mixed peak %.4f, rms %.5f over %lu samples", peak, sqrt(sum / total), total);
|
||||
sum = 0.0;
|
||||
peak = 0.0f;
|
||||
total = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDLCALL audio_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount)
|
||||
{
|
||||
@@ -445,6 +472,8 @@ static void SDLCALL audio_callback(void *userdata, SDL_AudioStream *stream, int
|
||||
if (!frames)
|
||||
frames = 1;
|
||||
mix(buffer, frames);
|
||||
if (audio_log)
|
||||
audio_log_level(buffer, frames * OUTPUT_CHANNELS);
|
||||
SDL_PutAudioStreamData(stream, buffer, (int)(frames * OUTPUT_CHANNELS * sizeof(float)));
|
||||
additional_amount -= (int)(frames * OUTPUT_CHANNELS * sizeof(float));
|
||||
}
|
||||
@@ -480,6 +509,7 @@ static void audio_start(void)
|
||||
return;
|
||||
audio_started = TRUE;
|
||||
master_volume = (float)config_real("audio.volume");
|
||||
audio_log = config_boolean("debug.audio_log");
|
||||
|
||||
if (config_boolean("audio.enabled") && platform_sdl_initialize())
|
||||
{
|
||||
@@ -491,6 +521,8 @@ static void audio_start(void)
|
||||
if (audio_stream)
|
||||
{
|
||||
SDL_ResumeAudioStreamDevice(audio_stream);
|
||||
platform_log("audio: device stream open, %d Hz, %d channels, volume %.2f", OUTPUT_RATE, OUTPUT_CHANNELS,
|
||||
master_volume);
|
||||
return;
|
||||
}
|
||||
platform_log("cannot open an audio device (%s); sound is silent", SDL_GetError());
|
||||
|
||||
@@ -220,6 +220,8 @@ static const struct config_setting config_settings[] =
|
||||
"Log texture uploads." },
|
||||
{ "debug.texture_no_cache", _config_boolean, "false", "HALO_TEXTURE_NO_CACHE", _environment_set_is_true, _platform_all,
|
||||
"Upload textures again every time they are used." },
|
||||
{ "debug.audio_log", _config_boolean, "false", "HALO_AUDIO_LOG", _environment_set_is_true, _platform_all,
|
||||
"Log the level of the mixed sound every five seconds." },
|
||||
{ "debug.sample_seconds", _config_real, "0.0", "HALO_SAMPLE", _environment_value, _platform_android,
|
||||
"Log where every game thread is this often, in seconds (read by the\n"
|
||||
"app, port/android/host/host_debug.c); 0 never." },
|
||||
|
||||
@@ -613,6 +613,21 @@ int halo_vr_look_vector(short local_player_index, float vector[3])
|
||||
return 1;
|
||||
}
|
||||
|
||||
void halo_vr_debug_look(short local_player_index, const float unit_looking[3], int in_vehicle)
|
||||
{
|
||||
static unsigned long long last_us;
|
||||
float head[3] = { 0.0f, 0.0f, 0.0f };
|
||||
int valid;
|
||||
unsigned long long now = now_us();
|
||||
|
||||
if (!vr.log_performance || local_player_index != 0 || now - last_us < 1000000)
|
||||
return;
|
||||
last_us = now;
|
||||
valid = halo_vr_look_vector(local_player_index, head);
|
||||
platform_log("VR look: head %s (%.2f %.2f %.2f), unit looking (%.2f %.2f %.2f)%s", valid ? "valid" : "INVALID",
|
||||
head[0], head[1], head[2], unit_looking[0], unit_looking[1], unit_looking[2], in_vehicle ? ", in a vehicle" : "");
|
||||
}
|
||||
|
||||
int halo_vr_look_delta(float *yaw, float *pitch)
|
||||
{
|
||||
/* past the head's tremble */
|
||||
@@ -683,6 +698,10 @@ int halo_vr_laser(short local_player_index, float start[3], float direction[3])
|
||||
return 0;
|
||||
}
|
||||
int halo_vr_look_vector(short local_player_index, float vector[3]) { (void)local_player_index; (void)vector; return 0; }
|
||||
void halo_vr_debug_look(short local_player_index, const float unit_looking[3], int in_vehicle)
|
||||
{
|
||||
(void)local_player_index; (void)unit_looking; (void)in_vehicle;
|
||||
}
|
||||
int halo_vr_look_delta(float *yaw, float *pitch) { *yaw = *pitch = 0.0f; return 0; }
|
||||
int halo_vr_hide_crosshair(void) { return 0; }
|
||||
|
||||
|
||||
@@ -3511,6 +3511,15 @@ void players_update_before_game(
|
||||
control_data.aiming_vector.k = vr_aim[2];
|
||||
}
|
||||
}
|
||||
{
|
||||
real unit_look[3];
|
||||
|
||||
unit_look[0] = unit->unit.looking_vector.i;
|
||||
unit_look[1] = unit->unit.looking_vector.j;
|
||||
unit_look[2] = unit->unit.looking_vector.k;
|
||||
halo_vr_debug_look(player->local_player_index, unit_look,
|
||||
unit->object.parent_object_index != NONE);
|
||||
}
|
||||
#endif
|
||||
control_data.throttle.i = action->throttle.i;
|
||||
control_data.throttle.j = action->throttle.j;
|
||||
|
||||
@@ -688,6 +688,10 @@ symbols in this file:
|
||||
#include "sound/game_sound.h"
|
||||
#include "vehicles.h"
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
#include "halo_vr.h"
|
||||
#endif
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
enum
|
||||
@@ -2971,6 +2975,22 @@ boolean unit_can_see_point(
|
||||
{
|
||||
can_see_point = TRUE;
|
||||
}
|
||||
#ifdef HALO_ANDROID
|
||||
else if (unit->unit.player_index != NONE && halo_vr_active())
|
||||
{
|
||||
/* VR: the head only points roughly where the eyes look, so it
|
||||
sees within a wider angle; and the weapon's laser targets too
|
||||
(the training's "target each of the flashing lights" asks for
|
||||
5 degrees) */
|
||||
real head_field_of_view = MAX(field_of_view, DEGREES_TO_RADIANS(15.f));
|
||||
|
||||
if (dot_product3d(&direction, &unit->unit.looking_vector)>cosine(head_field_of_view) ||
|
||||
dot_product3d(&direction, &unit->unit.aiming_vector)>cosine(field_of_view))
|
||||
{
|
||||
can_see_point = TRUE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return can_see_point;
|
||||
|
||||
Reference in new issue
Block a user