diff --git a/cores/libretro-mpv/Makefile b/cores/libretro-mpv/Makefile index 5b84e98581..a48ea471a7 100644 --- a/cores/libretro-mpv/Makefile +++ b/cores/libretro-mpv/Makefile @@ -114,7 +114,7 @@ endif CFLAGS += -I../../libretro-common/include OBJECTS := mpv-libretro.o -LDFLAGS += -lmpv -ldl -lm +LDFLAGS += -lmpv -lm CFLAGS += -Wall -pedantic all: $(TARGET) diff --git a/cores/libretro-mpv/README.md b/cores/libretro-mpv/README.md index 9ef302196c..369cc730ee 100644 --- a/cores/libretro-mpv/README.md +++ b/cores/libretro-mpv/README.md @@ -1,17 +1,46 @@ # libretro-mpv -mpv media player as a libretro core. A proof of concept release is now available. +mpv media player as a libretro core. A proof of concept release is now +available. -Aims to use features already established in mpv that are not currently available in Retroarch movieplayer. +Aims to use features already established in mpv that are not currently +available in Retroarch movieplayer. -I want to be able to use Retroarch as my movie player on my embedded devices (Raspberry Pi) and desktop using hardware acceleration without having to use Kodi or mpv directly. Thus allowing for a more integrated experience, and smaller root filesystem. +I want to be able to use Retroarch as my movie player on my embedded devices +(Raspberry Pi) and desktop using hardware acceleration without having to use +Kodi or mpv directly. Thus allowing for a more integrated experience, and +smaller root filesystem. ## Compiling -Retroarch must be compiled with `--disable-ffmpeg` to stop the integrated movieplayer from playing the input file. +### Overview + +Retroarch must be compiled with `--disable-ffmpeg` to stop the integrated +movieplayer from playing the input file. FFmpeg (preferably master branch) must be compiled with `--enable-shared`. mpv must be compiled with `--enable-libmpv-shared`. Then run `make` in the mpv-libretro folder. + +### Compiling with Mingw-w64 on Windows + +RetroArch must be compiled with `--disable-ffmpeg` and have OpenGL or +OpenGLES enabled. Compiling RetroArch is not described here. + +1. Open `Minwg-w64 64 bit` (not MSYS2 shell). +2. Install ffmpeg using `pacman -S mingw64/mingw-w64-x86_64-ffmpeg`. +3. Clone the ao_cb branch of https://github.com/deltabeard/mpv.git . This fork has the audio +callback patches required for libretro-mpv. +4. Follow the instructions at https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md#native-compilation-with-msys2 to compile mpv. +5. Download libretro-mpv release 0.3.alpha by either checking out the + `audio-cb-new` branch of `https://github.com/libretro/libretro-mpv.git` or + by downloading + https://github.com/libretro/libretro-mpv/archive/0.3.alpha.tar.gz . +6. Run `make` in the libretro-mpv folder. If using OpenGLES, run `make + platform=gles` instead. + +Overall, the dependencies required to build libretro-mpv are: +- ffmpeg 4.0 (provided by mingw64/mingw-w64-x86_64-ffmpeg) +- mpv from https://github.com/deltabeard/mpv.git fork. diff --git a/cores/libretro-mpv/mpv-libretro.c b/cores/libretro-mpv/mpv-libretro.c index 3f95acc50e..25d1d5998c 100644 --- a/cores/libretro-mpv/mpv-libretro.c +++ b/cores/libretro-mpv/mpv-libretro.c @@ -14,26 +14,22 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + #include #include #include #include #include -#include - -#ifdef HAVE_OPENGLES -#include -#endif #ifdef HAVE_LOCALE #include #endif #include -#include +#include #include + #include "version.h" static struct retro_hw_render_callback hw_render; @@ -49,20 +45,17 @@ static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; static mpv_handle *mpv; -static mpv_opengl_cb_context *mpv_gl; - -/* Keep track of the number of events in mpv queue */ -static unsigned int event_waiting = 0; +static mpv_render_context *mpv_gl; /* Save the current playback time for context changes */ -static int64_t *playback_time = 0; +static int64_t playback_time = 0; /* filepath required globaly as mpv is reopened on context change */ static char *filepath = NULL; static volatile int frame_queue = 0; -void queue_new_frame(void *cb_ctx) +void on_mpv_redraw(void *cb_ctx) { frame_queue++; } @@ -76,52 +69,59 @@ static void fallback_log(enum retro_log_level level, const char *fmt, ...) va_end(va); } -static void print_mpv_logs(void) +/** + * Process various events triggered by mpv, such as printing log messages. + * + * \param event_block Wait until the mpv triggers specified event. Should be + * NULL if no wait is required. + */ +static void process_mpv_events(mpv_event_id event_block) { - /* Print out mpv logs */ - if(event_waiting > 0) + do { - while(1) + mpv_event *mp_event = mpv_wait_event(mpv, 0); + if(event_block == MPV_EVENT_NONE && + mp_event->event_id == MPV_EVENT_NONE) + break; + + if(mp_event->event_id == event_block) + event_block = MPV_EVENT_NONE; + + if(mp_event->event_id == MPV_EVENT_LOG_MESSAGE) { - mpv_event *mp_event = mpv_wait_event(mpv, 0); - if(mp_event->event_id == MPV_EVENT_NONE) - break; - - if(mp_event->event_id == MPV_EVENT_LOG_MESSAGE) - { - struct mpv_event_log_message *msg = - (struct mpv_event_log_message *)mp_event->data; - log_cb(RETRO_LOG_INFO, "mpv: [%s] %s: %s", - msg->prefix, msg->level, msg->text); - } - else if(mp_event->event_id == MPV_EVENT_END_FILE) - { - struct mpv_event_end_file *eof = - (struct mpv_event_end_file *)mp_event->data; - - if(eof->reason == MPV_END_FILE_REASON_EOF) - environ_cb(RETRO_ENVIRONMENT_SHUTDOWN, NULL); -#if 0 - /* The following could be done instead if the file was not - * closed once the end was reached - allowing the user to seek - * back without reopening the file. - */ - struct retro_message ra_msg = { - "Finished playing file", 60 * 5, /* 5 seconds */ - }; - - environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &ra_msg);RETRO_ENVIRONMENT_SHUTDOWN -#endif - } - else - { - log_cb(RETRO_LOG_INFO, "mpv: %s\n", - mpv_event_name(mp_event->event_id)); - } + struct mpv_event_log_message *msg = + (struct mpv_event_log_message *)mp_event->data; + log_cb(RETRO_LOG_INFO, "mpv: [%s] %s: %s", + msg->prefix, msg->level, msg->text); } + else if(mp_event->event_id == MPV_EVENT_END_FILE) + { + struct mpv_event_end_file *eof = + (struct mpv_event_end_file *)mp_event->data; - event_waiting = 0; + if(eof->reason == MPV_END_FILE_REASON_EOF) + environ_cb(RETRO_ENVIRONMENT_SHUTDOWN, NULL); +#if 0 + /* The following could be done instead if the file was not + * closed once the end was reached - allowing the user to seek + * back without reopening the file. + */ + struct retro_message ra_msg = { + "Finished playing file", 60 * 5, /* 5 seconds */ + }; + + environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &ra_msg);RETRO_ENVIRONMENT_SHUTDOWN +#endif + } + else if(mp_event->event_id == MPV_EVENT_NONE) + continue; + else + { + log_cb(RETRO_LOG_INFO, "mpv: %s\n", + mpv_event_name(mp_event->event_id)); + } } + while(1); } static void *get_proc_address_mpv(void *fn_ctx, const char *name) @@ -134,38 +134,20 @@ static void *get_proc_address_mpv(void *fn_ctx, const char *name) void *proc_addr = (void *) hw_render.get_proc_address(name); #pragma GCC diagnostic pop -#ifdef HAVE_OPENGLES - /* EGL 1.4 (supported by the RPI firmware) does not necessarily return - * function pointers for core functions. - */ - if (!proc_addr) - { - void *h = dlopen("/opt/vc/lib/libGLESv2.so", RTLD_LAZY); - - if (!h) - h = dlopen("/usr/lib/libGLESv2.so", RTLD_LAZY); - - if (h) - { - proc_addr = dlsym(h, name); - dlclose(h); - } - } -#endif - if(proc_addr == NULL) log_cb(RETRO_LOG_ERROR, "Failure obtaining %s proc address\n", name); return proc_addr; } -static void on_mpv_events(void *mpv) -{ - event_waiting++; -} - void retro_init(void) { + if(mpv_client_api_version() != MPV_CLIENT_API_VERSION) + { + log_cb(RETRO_LOG_WARN, "libmpv version mismatch. Please update or " + "recompile mpv-libretro after updating libmpv."); + } + return; } @@ -210,6 +192,7 @@ void retro_get_system_av_info(struct retro_system_av_info *info) { float sampling_rate = 48000.0f; +#if 0 struct retro_variable var = { .key = "test_aspect" }; environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var); @@ -217,7 +200,7 @@ void retro_get_system_av_info(struct retro_system_av_info *info) if(environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) sampling_rate = strtof(var.value, NULL); - +#endif info->timing = (struct retro_system_timing) { .fps = 60.0, .sample_rate = sampling_rate, @@ -240,10 +223,12 @@ void retro_set_environment(retro_environment_t cb) environ_cb = cb; static const struct retro_variable vars[] = { - { "test_samplerate", "Sample Rate; 48000|30000|20000" }, +#if 0 + { "test_samplerate", "Sample Rate; 48000" }, { "test_opt0", "Test option #0; false|true" }, { "test_opt1", "Test option #1; 0" }, { "test_opt2", "Test option #2; 0|1|foo|3" }, +#endif { NULL, NULL }, }; @@ -258,6 +243,7 @@ void retro_set_environment(retro_environment_t cb) static void context_reset(void) { const char *cmd[] = {"loadfile", filepath, NULL}; + int ret; #ifdef HAVE_LOCALE setlocale(LC_NUMERIC, "C"); @@ -265,66 +251,68 @@ static void context_reset(void) mpv = mpv_create(); - if(mpv == NULL) + if(!mpv) { log_cb(RETRO_LOG_ERROR, "failed creating context\n"); exit(EXIT_FAILURE); } - if(mpv_initialize(mpv) < 0) + if((ret = mpv_initialize(mpv)) < 0) { - log_cb(RETRO_LOG_ERROR, "mpv init failed\n"); + log_cb(RETRO_LOG_ERROR, "mpv init failed: %s\n", mpv_error_string(ret)); exit(EXIT_FAILURE); } - /* When normal mpv events are available. */ - mpv_set_wakeup_callback(mpv, on_mpv_events, NULL); - - if(mpv_request_log_messages(mpv, "v") < 0) - log_cb(RETRO_LOG_ERROR, "mpv logging failed\n"); - - /* The OpenGL API is somewhat separate from the normal mpv API. This only - * returns NULL if no OpenGL support is compiled. - */ - mpv_gl = mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); - - if(mpv_gl == NULL) + if((ret = mpv_request_log_messages(mpv, "v")) < 0) { - log_cb(RETRO_LOG_ERROR, "failed to create mpv GL API handle\n"); + log_cb(RETRO_LOG_ERROR, "mpv logging failed: %s\n", + mpv_error_string(ret)); + } + + mpv_render_param params[] = { + {MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_OPENGL}, + {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &(mpv_opengl_init_params){ + .get_proc_address = get_proc_address_mpv, + }}, + {0} + }; + + if((ret = mpv_render_context_create(&mpv_gl, mpv, params)) < 0) + { + log_cb(RETRO_LOG_ERROR, "failed to initialize mpv GL context: %s\n", + mpv_error_string(ret)); goto err; } - if(mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address_mpv, NULL) < 0) - { - log_cb(RETRO_LOG_ERROR, "failed to initialize mpv GL context\n"); - goto err; - } + mpv_render_context_set_update_callback(mpv_gl, on_mpv_redraw, NULL); - /* Actually using the opengl_cb state has to be explicitly requested. - * Otherwise, mpv will create a separate platform window. - */ - if(mpv_set_option_string(mpv, "vo", "opengl-cb") < 0) - { - log_cb(RETRO_LOG_ERROR, "failed to set video output to OpenGL\n"); - goto err; - } - - mpv_opengl_cb_set_update_callback(mpv_gl, queue_new_frame, NULL); - - mpv_set_option_string(mpv, "ao", "audio-cb"); + mpv_set_option_string(mpv, "ao", "libmpv"); /* Attempt to enable hardware acceleration. MPV will fallback to software * decoding on failure. */ - if(mpv_set_option_string(mpv, "hwdec", "auto") < 0) - log_cb(RETRO_LOG_ERROR, "failed to set hwdec option\n"); - - if(mpv_command(mpv, cmd) != 0) + if((ret = mpv_set_option_string(mpv, "hwdec", "auto")) < 0) { - log_cb(RETRO_LOG_ERROR, "failed to issue mpv_command to load file\n"); + log_cb(RETRO_LOG_ERROR, "failed to set hwdec option: %s\n", + mpv_error_string(ret)); + } + + if((ret = mpv_command(mpv, cmd)) != 0) + { + log_cb(RETRO_LOG_ERROR, "mpv_command failed to load input file: %s\n", + mpv_error_string(ret)); goto err; } + /* TODO #2: Check for the highest samplerate in audio stream, and use that. + * Fall back to 48kHz otherwise. + * We currently force the audio to be sampled at 48KHz. + */ + mpv_set_option_string(mpv, "af", "format=s16:48000:stereo"); + //mpv_set_option_string(mpv, "opengl-swapinterval", "0"); + + /* Process any events whilst we wait for playback to begin. */ + process_mpv_events(MPV_EVENT_NONE); /* Keep trying until mpv accepts the property. This is done to seek to the * point in the file after the previous context was destroyed. If no @@ -332,20 +320,14 @@ static void context_reset(void) * * This also seems to fix some black screen issues. */ - while(mpv_set_property(mpv, - "playback-time", MPV_FORMAT_INT64, &playback_time) < 0) + if(playback_time != 0) { - /* Garbage fix to overflowing log */ - usleep(10); + process_mpv_events(MPV_EVENT_PLAYBACK_RESTART); + while(mpv_set_property(mpv, + "playback-time", MPV_FORMAT_INT64, &playback_time) < 0) + {} } - /* TODO #2: Check for the highest samplerate in audio stream, and use that. - * Fall back to 48kHz otherwise. - * We currently force the audio to be sampled at 48KHz. - */ - mpv_set_option_string(mpv, "audio-samplerate", "48000"); - mpv_set_option_string(mpv, "opengl-swapinterval", "0"); - /* The following works best when vsync is switched off in Retroarch. */ //mpv_set_option_string(mpv, "video-sync", "display-resample"); //mpv_set_option_string(mpv, "display-fps", "60"); @@ -356,14 +338,14 @@ static void context_reset(void) err: /* Print mpv logs to see why mpv failed. */ - print_mpv_logs(); + process_mpv_events(MPV_EVENT_NONE); exit(EXIT_FAILURE); } static void context_destroy(void) { mpv_get_property(mpv, "playback-time", MPV_FORMAT_INT64, &playback_time); - mpv_opengl_cb_uninit_gl(mpv_gl); + mpv_render_context_free(mpv_gl); mpv_terminate_destroy(mpv); log_cb(RETRO_LOG_INFO, "Context destroyed.\n"); } @@ -418,20 +400,36 @@ static void audio_callback(double fps) { /* Obtain len samples to reduce lag. */ int len = 48000 / (int)floor(fps); - static int16_t frames[512]; + const unsigned int buff_len = 1024; + uint8_t buff[buff_len]; - while(len > 0) + if(len < 4) + len = 4; + + do { - int mpv_len = mpv_audio_callback(&frames, len > 512 ? 512*2 : len*2); - //printf("mpv cb: %d\n", mpv_len); - if(mpv_len < 1) - return; + len = len - (len % 4); - len -= mpv_len; + int ret = mpv_audio_callback(mpv, &buff, + len > buff_len ? buff_len : len); - //printf("acb: %lu\n", audio_batch_cb(frames, mpv_len)); - audio_batch_cb(frames, mpv_len); + if(ret < 0) + { + log_cb(RETRO_LOG_ERROR, "mpv encountered an error in audio " + "callback: %d.\n", ret); + return; + } + + /* mpv may refuse to buffer audio if the first video frame has not + * displayed yet. */ + if(ret == 0) + return; + + len -= ret; + + audio_batch_cb((const int16_t*)buff, ret); } + while(len > 4); } static void retropad_update_input(void) @@ -551,17 +549,25 @@ void retro_run(void) updated_video_dimensions = true; } - print_mpv_logs(); - retropad_update_input(); /* TODO #2: Implement an audio callback feature in to libmpv */ audio_callback(container_fps); + process_mpv_events(MPV_EVENT_NONE); + #if 1 if(frame_queue > 0) { - mpv_opengl_cb_draw(mpv_gl, hw_render.get_current_framebuffer(), width, height); + mpv_render_param params[] = { + {MPV_RENDER_PARAM_OPENGL_FBO, &(mpv_opengl_fbo){ + .fbo = hw_render.get_current_framebuffer(), + .w = width, + .h = height, + }}, + {0} + }; + mpv_render_context_render(mpv_gl, params); video_cb(RETRO_HW_FRAME_BUFFER_VALID, width, height, 0); frame_queue--; } @@ -571,6 +577,7 @@ void retro_run(void) mpv_opengl_cb_draw(mpv_gl, hw_render.get_current_framebuffer(), width, height); video_cb(RETRO_HW_FRAME_BUFFER_VALID, width, height, 0); #endif + return; } @@ -607,10 +614,19 @@ bool retro_load_game(const struct retro_game_info *info) { 0 }, }; + if(info->path == NULL) + return false; + /* Copy the file path to a global variable as we need it in context_reset() * where mpv is initialised. */ - filepath = strdup(info->path); + if((filepath = malloc(strlen(info->path)+1)) == NULL) + { + log_cb(RETRO_LOG_ERROR, "Unable to allocate memory for filepath\n"); + return false; + } + + strcpy(filepath,info->path); environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); diff --git a/cores/libretro-mpv/version.h b/cores/libretro-mpv/version.h index 203d2c70db..494a513a0b 100644 --- a/cores/libretro-mpv/version.h +++ b/cores/libretro-mpv/version.h @@ -1,3 +1,3 @@ #ifndef LIBRETRO_MPV_VERSION -#define LIBRETRO_MPV_VERSION "0.2.alpha" +#define LIBRETRO_MPV_VERSION "0.3.alpha" #endif