From 4f18e7927bacd2e887f8cca48a967804ce7adf86 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 8 Oct 2020 00:35:37 +0200 Subject: demux: add a POS I regret doing this so much, it's fucking garbage. Fixes: #5100 --- demux/demux.c | 4 ++ demux/demux_midi.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++ wscript | 5 ++ wscript_build.py | 1 + 4 files changed, 190 insertions(+) create mode 100644 demux/demux_midi.c diff --git a/demux/demux.c b/demux/demux.c index e4dda43cf0..1c09d1c1bf 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -66,6 +66,7 @@ extern const demuxer_desc_t demuxer_desc_rar; extern const demuxer_desc_t demuxer_desc_libarchive; extern const demuxer_desc_t demuxer_desc_null; extern const demuxer_desc_t demuxer_desc_timeline; +extern const demuxer_desc_t demuxer_desc_midi; static const demuxer_desc_t *const demuxer_list[] = { &demuxer_desc_disc, @@ -78,6 +79,9 @@ static const demuxer_desc_t *const demuxer_list[] = { &demuxer_desc_libarchive, #endif &demuxer_desc_lavf, +#if HAVE_FLUIDSYNTH + &demuxer_desc_midi, +#endif &demuxer_desc_mf, &demuxer_desc_playlist, &demuxer_desc_null, diff --git a/demux/demux_midi.c b/demux/demux_midi.c new file mode 100644 index 0000000000..a2d6beb0ff --- /dev/null +++ b/demux/demux_midi.c @@ -0,0 +1,180 @@ +#include + +#include + +#include "codec_tags.h" +#include "demux.h" +#include "stream/stream.h" + +// Arbitrary. +#define FRAME_SAMPLES 1024 +#define SAMPLERATE 48000 + +struct priv { + struct sh_stream *sh; + fluid_settings_t *settings; + fluid_synth_t *synth; + fluid_player_t *player; + uint64_t samples; + struct demux_packet *first_pkt; +}; + +static bool check_midi(uint8_t *buf, int size) +{ + if (size < 4 + 4 + 6 + 4) + return false; + if (memcmp(buf, "MThd", 4)) + return false; + if (buf[4] || buf[5] || buf[6] || buf[7] != 6) + return false; // length must always be 6 + if (buf[8] || buf[9] > 2) + return false; // version is always 0/1/2 + if (memcmp(&buf[4 + 4 + 6], "MTrk", 4)) + return false; // expect a MTrk chunk to follow + // Regarding bit 15 (SMPTE format flag): fluidsynth doesn't support it + int division = (buf[12] << 8) | buf[13]; + return division > 0 && !(division & (1 << 15)); +} + +static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **pkt) +{ + struct priv *p = demuxer->priv; + + if (p->first_pkt) { + *pkt = p->first_pkt; + p->first_pkt = NULL; + return true; + } + + if (fluid_player_get_status(p->player) != FLUID_PLAYER_PLAYING) + return false; + + struct demux_packet *dp = new_demux_packet(FRAME_SAMPLES * 4 * 2); + if (!dp) + return true; + + fluid_synth_write_float(p->synth, FRAME_SAMPLES, + dp->buffer, 0, 2, + dp->buffer, 1, 2); + + dp->pts = p->samples / (double)p->sh->codec->samplerate; + p->samples += FRAME_SAMPLES; + + dp->stream = p->sh->index; + dp->keyframe = true; + *pkt = dp; + + return true; +} + +static void d_close(struct demuxer *demuxer) +{ + struct priv *p = demuxer->priv; + + if (p->player) + delete_fluid_player(p->player); + if (p->synth) + delete_fluid_synth(p->synth); + if (p->settings) + delete_fluid_settings(p->settings); + talloc_free(p->first_pkt); +} + +static void no(int level, const char *message, void *data) +{ +} + +static int try_open_file(struct demuxer *demuxer, enum demux_check check) +{ + struct priv *p = talloc_zero(demuxer, struct priv); + demuxer->priv = p; + + uint8_t probe[STREAM_BUFFER_SIZE]; + int len = stream_read_peek(demuxer->stream, probe, sizeof(probe)); + if (len < 1 || !check_midi(probe, len)) + return -1; + + bstr data = stream_read_complete(demuxer->stream, demuxer, 1000000); + if (data.start == NULL) + return -1; + + // Another idiot API with a global log callback and defaulting to stderr (or + // stdout on win32 - lol?). Shut it up to disable particularly stupid + // messages, such as about SDL (wtf? oh yes, they mess with SDL's fucking + // stupid global state too, even if you're not asking for it). + int fucking_stupid[] = {FLUID_PANIC, FLUID_ERR, FLUID_WARN, FLUID_INFO, + FLUID_DBG}; + for (int n = 0; n < MP_ARRAY_SIZE(fucking_stupid); n++) + fluid_set_log_function(fucking_stupid[n], no, NULL); + + p->settings = new_fluid_settings(); + if (!p->settings) + goto error; + if (fluid_settings_setstr(p->settings, "player.timing-source", "sample")) + goto error; + if (fluid_settings_setnum(p->settings, "synth.sample-rate", SAMPLERATE)) + goto error; + p->synth = new_fluid_synth(p->settings); + if (!p->synth) + goto error; + p->player = new_fluid_player(p->synth); + if (!p->player) + goto error; + + char *soundfont; + if (fluid_settings_dupstr(p->settings, "synth.default-soundfont", &soundfont)) + soundfont = NULL; + if (!soundfont) { + MP_ERR(demuxer, "No sound font available.\n"); + goto error; + } + int soundfont_st = fluid_synth_sfload(p->synth, soundfont, 1); + fluid_free(soundfont); + if (!soundfont_st) { + MP_ERR(demuxer, "Failed to load sound font available.\n"); + goto error; + } + + if (fluid_player_add_mem(p->player, data.start, data.len)) + goto error; + + p->sh = demux_alloc_sh_stream(STREAM_AUDIO); + struct mp_codec_params *c = p->sh->codec; + c->channels = (struct mp_chmap)MP_CHMAP_INIT_STEREO; + c->samplerate = SAMPLERATE; + c->native_tb_num = 1; + c->native_tb_den = c->samplerate; + mp_set_pcm_codec(p->sh->codec, true, true, 32, false); + + demux_add_sh_stream(demuxer, p->sh); + + if (fluid_player_play(p->player)) + goto error; + + // Fluidsynth has some sort of internal playlist, and it advances to the + // current one only if you "pull" some samples. That means we don't know + // whether the MIDI file can even be loaded by Fluidsynth until we read + // same data. + // This heuristic may fail on very short MIDI files. + d_read_packet(demuxer, &p->first_pkt); + if (fluid_player_get_status(p->player) != FLUID_PLAYER_PLAYING) + goto error; + + talloc_free(data.start); + demuxer->seekable = false; + demux_close_stream(demuxer); + + return 0; + +error: + MP_ERR(demuxer, "Fluidsynth failed to initialize.\n"); + return -1; +} + +const struct demuxer_desc demuxer_desc_midi = { + .name = "midi", + .desc = "MIDI via fluidsynth", + .open = try_open_file, + .close = d_close, + .read_packet = d_read_packet, +}; diff --git a/wscript b/wscript index 6daf92e60b..0467206462 100644 --- a/wscript +++ b/wscript @@ -383,6 +383,11 @@ iconv support use --disable-iconv.", 'desc': 'SDL2 gamepad input', 'deps': 'sdl2', 'func': check_true, + }, { + 'name': '--fluidsynth', + 'desc': 'MIDI file playback support via fluidsynth', + 'func': check_pkg_config('fluidsynth', '>= 2.1.5'), + 'default': 'disable', } ] diff --git a/wscript_build.py b/wscript_build.py index e8428fa9dc..a12badaf24 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -276,6 +276,7 @@ def build(ctx): ( "demux/demux_lavf.c" ), ( "demux/demux_libarchive.c", "libarchive" ), ( "demux/demux_mf.c" ), + ( "demux/demux_midi.c", "fluidsynth" ), ( "demux/demux_mkv.c" ), ( "demux/demux_mkv_timeline.c" ), ( "demux/demux_null.c" ), -- cgit v1.2.3