mirror of
https://github.com/libretro/blastem.git
synced 2026-11-07 10:15:58 -10:00
* libretro: do not ask the emulator to stop when presenting a frame it did not A frame ends when the emulator asks to be let go: render_framebuffer_updated() hands the frontend the picture and calls system_request_exit(), which is what returns control from retro_run(). A system that comes back without presenting anything - the media player, which has no video at all - would leave the frontend without a frame, so this file presents one on its behalf, and it did that by calling render_framebuffer_updated() as well. That request to stop does not expire. It is still standing when the next retro_run() enters the emulation loop, which is written as while (!gen->m68k->should_return) so the loop ends before a single instruction runs, clears the flag, and returns with nothing presented - which makes this file present a frame again, and ask the emulator to stop again. One missed frame and the machine never runs again. A Mega Drive never hit it because it presents a frame on the very first call. A Sega CD does not: the call that starts it returns while the BIOS is still bringing the machine up, and the deadlock closes over it. Sonic CD sat on a black screen with both 68000s frozen - the main one at 420 cycles, the sub one still held in reset - for as long as it was left running. Presenting and asking to stop are two things, so separate them: the frame this file owes the frontend goes out through present_framebuffer(), and only the emulator's own frame end asks for the exit. The media player does not need the request either; it sets should_return itself in the IS_LIB build. Sonic CD now boots and runs. Measured over 600 frames: 95.7% of the audio the declared sample rate calls for, where before there was none at all, and the picture changes rather than staying the black frame it started on. * libretro: mix audio the way the standalone build does render_do_audio_ready() was the whole mixer: it swapped a source's buffers and, once every source had one ready, mixed four frames and handed them over. Four is what a source's buffer holds in the sync-to-audio path, so with the FM and the PSG - clocked from the same master clock, resampled to the same rate - the two arrive together and every buffer is consumed as it is filled. Nothing else about a Mega Drive holds. A Sega CD adds CD-DA and the PCM chip, which fill at a pace of their own, so a source can fill twice before another fills once. The second fill swaps over a buffer nobody mixed: four frames gone, which is what a click is. The standalone does not have this problem because its emulation thread waits on the audio callback there, or breaks the wait with audio_deadlock_hack(), and neither is available to a core that must return from retro_run(). Since a frame here ends when the VDP hands over a framebuffer, audio does not have to pace anything, so use the arrangement render_audio.c already has for that case: report render_is_audio_sync() as false, give each source a ring buffer of its own, and let render_do_audio_ready() do nothing but publish what has been written. retro_run() then mixes with mix_and_convert() and hands the frontend a batch, the same call the standalone's audio callback makes. How much to mix is what every source that has anything can cover, which audio_buffered() reports; the rest stays queued rather than being mixed past what a source has written. Sources with nothing at all are left out of that count, or a CD-DA source with no track playing - which never writes anything, ever - would hold up the mixing forever, and taking the busiest source instead pads the ones behind it with silence. Measured over 600 frames each. Sonic 1: 100.0% of the frames the declared sample rate calls for, before and after, in 1200 batches rather than 133305. Sonic CD, which has all four sources going: 95.7% before, 99.8% after, and no silent frame in either. Neither shows a step between samples larger than a real waveform takes. It also fixes the soft reset, which was reported separately as "reset the game freezes". Measured with the same harness, Sonic 1, 180 frames either side of retro_reset(): before this change the core delivers 159876 audio frames before the reset and 0 after it, and the picture stops changing altogether - the frontend still gets its frames, but every one of them is the same. After this change it delivers 159106 audio frames after the reset and the picture goes on changing. That follows from the same cause. The old path mixes only when all_sources_ready() says every source has a buffer, and it reports render_is_audio_sync() as true, so the emulator throttles itself to audio being consumed. A reset leaves one source that never fills again, the AND never holds, nothing is consumed, and the emulation stops waiting for an audio callback that cannot come. Mixing what each source actually has, and not pacing on audio at all, removes both halves of that.
62 lines
2.1 KiB
C
62 lines
2.1 KiB
C
#ifndef RENDER_AUDIO_H_
|
|
#define RENDER_AUDIO_H_
|
|
|
|
#include <stdint.h>
|
|
typedef enum {
|
|
RENDER_AUDIO_S16,
|
|
RENDER_AUDIO_FLOAT,
|
|
RENDER_AUDIO_UNKNOWN
|
|
} render_audio_format;
|
|
|
|
typedef struct {
|
|
const char *name;
|
|
void *opaque;
|
|
int16_t *front;
|
|
int16_t *back;
|
|
double dt;
|
|
uint64_t buffer_fraction;
|
|
uint64_t buffer_inc;
|
|
float gain_mult;
|
|
uint32_t buffer_pos;
|
|
uint32_t read_start;
|
|
uint32_t read_end;
|
|
uint32_t lowpass_alpha;
|
|
uint32_t mask;
|
|
int16_t last_left;
|
|
int16_t last_right;
|
|
uint8_t num_channels;
|
|
uint8_t front_populated;
|
|
} audio_source;
|
|
|
|
//public interface
|
|
audio_source *render_audio_source(const char *name, uint64_t master_clock, uint64_t sample_divider, uint8_t channels);
|
|
void render_audio_source_gaindb(audio_source *src, float gain);
|
|
void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider);
|
|
void render_put_mono_sample(audio_source *src, int16_t value);
|
|
void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right);
|
|
void render_pause_source(audio_source *src);
|
|
void render_resume_source(audio_source *src);
|
|
void render_free_source(audio_source *src);
|
|
void render_end_audio(void);
|
|
void render_save_audio(char *path);
|
|
//interface for render backends
|
|
void render_audio_initialized(render_audio_format format, uint32_t rate, uint8_t channels, uint32_t buffer_size, int sample_size);
|
|
int mix_and_convert(unsigned char *byte_stream, int len, int *min_remaining_out);
|
|
uint32_t audio_buffered(void);
|
|
uint8_t all_sources_ready(void);
|
|
void render_audio_adjust_speed(float adjust_ratio);
|
|
//to be implemented by render backend
|
|
uint8_t render_is_audio_sync(void);
|
|
void render_buffer_consumed(audio_source *src);
|
|
void *render_new_audio_opaque(void);
|
|
void render_free_audio_opaque(void *opaque);
|
|
void render_lock_audio(void);
|
|
void render_unlock_audio(void);
|
|
uint32_t render_min_buffered(void);
|
|
uint32_t render_audio_syncs_per_sec(void);
|
|
void render_audio_created(audio_source *src);
|
|
void render_do_audio_ready(audio_source *src);
|
|
void render_source_paused(audio_source *src, uint8_t remaining_sources);
|
|
void render_source_resumed(audio_source *src);
|
|
#endif //RENDER_AUDIO_H_
|