Add experimental MPV player backend (#161)

Experimental MPV player backend

Related to #14, #22, & #85

You can switch to MPV in advanced settings and toggle using hardware
decoding or not.

This uses code and buildscripts from
https://github.com/mpv-android/mpv-android, plus other third party
libraries to build MPV
This commit is contained in:
damontecres 2025-11-16 18:46:25 -05:00 committed by GitHub
parent d7b333e519
commit 6be2662d4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
84 changed files with 3987 additions and 289 deletions

View file

@ -0,0 +1,133 @@
#include <stdlib.h>
#include <string>
#include <jni.h>
#include <android/bitmap.h>
#include <mpv/client.h>
extern "C" {
#include <libswscale/swscale.h>
};
#include "jni_utils.h"
#include "globals.h"
#include "log.h"
extern "C" {
jni_func(jobject, grabThumbnail, jint dimension);
};
static inline mpv_node make_node_str(const char *s)
{
mpv_node r{};
r.format = MPV_FORMAT_STRING;
r.u.string = const_cast<char*>(s);
return r;
}
jni_func(jobject, grabThumbnail, jint dimension) {
CHECK_MPV_INIT();
mpv_node result{};
{
mpv_node c{}, c_args[2];
mpv_node_list c_array{};
c_args[0] = make_node_str("screenshot-raw");
c_args[1] = make_node_str("video");
c_array.num = 2;
c_array.values = c_args;
c.format = MPV_FORMAT_NODE_ARRAY;
c.u.list = &c_array;
if (mpv_command_node(g_mpv, &c, &result) < 0) {
ALOGE("screenshot-raw command failed");
return NULL;
}
}
// extract relevant property data from the node map mpv returns
int w = 0, h = 0, stride = 0;
bool format_ok = false;
struct mpv_byte_array *data = NULL;
do {
if (result.format != MPV_FORMAT_NODE_MAP)
break;
for (int i = 0; i < result.u.list->num; i++) {
std::string key(result.u.list->keys[i]);
const mpv_node *val = &result.u.list->values[i];
if (key == "w" || key == "h" || key == "stride") {
if (val->format != MPV_FORMAT_INT64)
break;
if (key == "w")
w = val->u.int64;
else if (key == "h")
h = val->u.int64;
else
stride = val->u.int64;
} else if (key == "format") {
if (val->format != MPV_FORMAT_STRING)
break;
format_ok = !strcmp(val->u.string, "bgr0");
} else if (key == "data") {
if (val->format != MPV_FORMAT_BYTE_ARRAY)
break;
data = val->u.ba;
}
}
} while (0);
if (!w || !h || !stride || !format_ok || !data) {
ALOGE("extracting data failed");
mpv_free_node_contents(&result);
return NULL;
}
ALOGV("screenshot w:%d h:%d stride:%d", w, h, stride);
// crop to square
int crop_left = 0, crop_top = 0;
int new_w = w, new_h = h;
if (w > h) {
crop_left = (w - h) / 2;
new_w = h;
} else {
crop_top = (h - w) / 2;
new_h = w;
}
ALOGV("cropped w:%u h:%u", new_w, new_h);
uint8_t *new_data = reinterpret_cast<uint8_t*>(data->data);
new_data += crop_left * sizeof(uint32_t); // move begin rightwards
new_data += stride * crop_top; // move begin downwards
// convert & scale to appropriate size
struct SwsContext *ctx = sws_getContext(
new_w, new_h, AV_PIX_FMT_BGR0,
dimension, dimension, AV_PIX_FMT_RGB32,
SWS_BICUBIC, NULL, NULL, NULL);
if (!ctx) {
mpv_free_node_contents(&result);
return NULL;
}
jintArray arr = env->NewIntArray(dimension * dimension);
jint *scaled = env->GetIntArrayElements(arr, NULL);
uint8_t *src_p[4] = { new_data }, *dst_p[4] = { (uint8_t*) scaled };
int src_stride[4] = { stride },
dst_stride[4] = { (int) sizeof(jint) * dimension };
sws_scale(ctx, src_p, src_stride, 0, new_h, dst_p, dst_stride);
sws_freeContext(ctx);
mpv_free_node_contents(&result); // frees data->data
// create android.graphics.Bitmap
env->ReleaseIntArrayElements(arr, scaled, 0);
jobject bitmap_config =
env->GetStaticObjectField(android_graphics_Bitmap_Config, android_graphics_Bitmap_Config_ARGB_8888);
jobject bitmap =
env->CallStaticObjectMethod(android_graphics_Bitmap, android_graphics_Bitmap_createBitmap,
arr, dimension, dimension, bitmap_config);
env->DeleteLocalRef(arr);
env->DeleteLocalRef(bitmap_config);
return bitmap;
}