mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
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:
parent
d7b333e519
commit
6be2662d4e
84 changed files with 3987 additions and 289 deletions
82
app/src/main/jni/Android.mk
Normal file
82
app/src/main/jni/Android.mk
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
|
||||
PREFIX = $(PREFIX32)
|
||||
endif
|
||||
ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
|
||||
PREFIX = $(PREFIX64)
|
||||
endif
|
||||
ifeq ($(TARGET_ARCH_ABI),x86_64)
|
||||
PREFIX = $(PREFIX_X64)
|
||||
endif
|
||||
ifeq ($(TARGET_ARCH_ABI),x86)
|
||||
PREFIX = $(PREFIX_X86)
|
||||
endif
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libswresample
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libpostproc
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
# only include if library file exists
|
||||
ifneq (,$(wildcard $(LOCAL_SRC_FILES)))
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
endif
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavutil
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavcodec
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavformat
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libswscale
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavfilter
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavdevice
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
LOCAL_EXPORT_C_INCLUDES := $(PREFIX)/include
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libmpv
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/libmpv.so
|
||||
LOCAL_EXPORT_C_INCLUDES := $(PREFIX)/include
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := libplayer
|
||||
LOCAL_CFLAGS := -Werror
|
||||
LOCAL_CPPFLAGS += -std=c++11
|
||||
LOCAL_SRC_FILES := \
|
||||
main.cpp \
|
||||
render.cpp \
|
||||
log.cpp \
|
||||
jni_utils.cpp \
|
||||
property.cpp \
|
||||
event.cpp \
|
||||
thumbnail.cpp
|
||||
LOCAL_LDLIBS := -llog -lGLESv3 -lEGL -latomic
|
||||
LOCAL_SHARED_LIBRARIES := swscale avcodec mpv
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
17
app/src/main/jni/Application.mk
Normal file
17
app/src/main/jni/Application.mk
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
APP_ABI :=
|
||||
ifneq ($(PREFIX32),)
|
||||
APP_ABI += armeabi-v7a
|
||||
endif
|
||||
ifneq ($(PREFIX64),)
|
||||
APP_ABI += arm64-v8a
|
||||
endif
|
||||
ifneq ($(PREFIX_X64),)
|
||||
APP_ABI += x86_64
|
||||
endif
|
||||
ifneq ($(PREFIX_X86),)
|
||||
APP_ABI += x86
|
||||
endif
|
||||
|
||||
APP_PLATFORM := android-21
|
||||
APP_STL := c++_shared
|
||||
APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true
|
||||
16
app/src/main/jni/README.md
Normal file
16
app/src/main/jni/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# JNI bindings for libmpv
|
||||
|
||||
The files in this directory are copied from https://github.com/mpv-android/mpv-android/tree/master/app/src/main/jni
|
||||
|
||||
## License & copyright
|
||||
|
||||
The files are copyrighted and used under MIT license below:
|
||||
|
||||
Copyright (c) 2016 Ilya Zhuravlev
|
||||
Copyright (c) 2016 sfan5 <sfan5@live.de>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
120
app/src/main/jni/event.cpp
Normal file
120
app/src/main/jni/event.cpp
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
#include <jni.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "jni_utils.h"
|
||||
#include "log.h"
|
||||
|
||||
static void sendPropertyUpdateToJava(JNIEnv *env, mpv_event_property *prop)
|
||||
{
|
||||
jstring jprop = env->NewStringUTF(prop->name);
|
||||
jstring jvalue = NULL;
|
||||
switch (prop->format) {
|
||||
case MPV_FORMAT_NONE:
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_S, jprop);
|
||||
break;
|
||||
case MPV_FORMAT_FLAG:
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sb, jprop,
|
||||
(jboolean) (*(int*)prop->data != 0));
|
||||
break;
|
||||
case MPV_FORMAT_INT64:
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sl, jprop,
|
||||
(jlong) *(int64_t*)prop->data);
|
||||
break;
|
||||
case MPV_FORMAT_DOUBLE:
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sd, jprop,
|
||||
(jdouble) *(double*)prop->data);
|
||||
break;
|
||||
case MPV_FORMAT_STRING:
|
||||
jvalue = env->NewStringUTF(*(const char**)prop->data);
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_SS, jprop, jvalue);
|
||||
break;
|
||||
default:
|
||||
ALOGV("sendPropertyUpdateToJava: Unknown property update format received in callback: %d!", prop->format);
|
||||
break;
|
||||
}
|
||||
if (jprop)
|
||||
env->DeleteLocalRef(jprop);
|
||||
if (jvalue)
|
||||
env->DeleteLocalRef(jvalue);
|
||||
}
|
||||
|
||||
static void sendEventToJava(JNIEnv *env, int event)
|
||||
{
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_event, event);
|
||||
}
|
||||
|
||||
static void sendEndFileEventToJava(JNIEnv *env, mpv_event_end_file *event)
|
||||
{
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_end_file_event, event->reason, event->error);
|
||||
}
|
||||
|
||||
static void sendLogMessageToJava(JNIEnv *env, mpv_event_log_message *msg)
|
||||
{
|
||||
// filter the most obvious cases of invalid utf-8, since Java would choke on it
|
||||
const auto invalid_utf8 = [] (unsigned char c) {
|
||||
return c == 0xc0 || c == 0xc1 || c >= 0xf5;
|
||||
};
|
||||
for (int i = 0; msg->text[i]; i++) {
|
||||
if (invalid_utf8(static_cast<unsigned char>(msg->text[i])))
|
||||
return;
|
||||
}
|
||||
|
||||
jstring jprefix = env->NewStringUTF(msg->prefix);
|
||||
jstring jtext = env->NewStringUTF(msg->text);
|
||||
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_logMessage_SiS,
|
||||
jprefix, (jint) msg->log_level, jtext);
|
||||
|
||||
if (jprefix)
|
||||
env->DeleteLocalRef(jprefix);
|
||||
if (jtext)
|
||||
env->DeleteLocalRef(jtext);
|
||||
}
|
||||
|
||||
void *event_thread(void *arg)
|
||||
{
|
||||
JNIEnv *env = NULL;
|
||||
acquire_jni_env(g_vm, &env);
|
||||
if (!env)
|
||||
die("failed to acquire java env");
|
||||
|
||||
while (1) {
|
||||
mpv_event *mp_event;
|
||||
mpv_event_property *mp_property = NULL;
|
||||
mpv_event_log_message *msg = NULL;
|
||||
mpv_event_end_file *mp_end_file = NULL;
|
||||
|
||||
mp_event = mpv_wait_event(g_mpv, -1.0);
|
||||
|
||||
if (g_event_thread_request_exit)
|
||||
break;
|
||||
|
||||
if (mp_event->event_id == MPV_EVENT_NONE)
|
||||
continue;
|
||||
|
||||
switch (mp_event->event_id) {
|
||||
case MPV_EVENT_LOG_MESSAGE:
|
||||
msg = (mpv_event_log_message*)mp_event->data;
|
||||
ALOGV("[%s:%s] %s", msg->prefix, msg->level, msg->text);
|
||||
sendLogMessageToJava(env, msg);
|
||||
break;
|
||||
case MPV_EVENT_PROPERTY_CHANGE:
|
||||
mp_property = (mpv_event_property*)mp_event->data;
|
||||
sendPropertyUpdateToJava(env, mp_property);
|
||||
break;
|
||||
case MPV_EVENT_END_FILE:
|
||||
mp_end_file = (mpv_event_end_file*)mp_event->data;
|
||||
sendEndFileEventToJava(env, mp_end_file);
|
||||
default:
|
||||
ALOGV("event: %s\n", mpv_event_name(mp_event->event_id));
|
||||
sendEventToJava(env, mp_event->event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_vm->DetachCurrentThread();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
3
app/src/main/jni/event.h
Normal file
3
app/src/main/jni/event.h
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
void *event_thread(void *arg);
|
||||
7
app/src/main/jni/globals.h
Normal file
7
app/src/main/jni/globals.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
extern JavaVM *g_vm;
|
||||
extern mpv_handle *g_mpv;
|
||||
extern std::atomic<bool> g_event_thread_request_exit;
|
||||
52
app/src/main/jni/jni_utils.cpp
Normal file
52
app/src/main/jni/jni_utils.cpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#define UTIL_EXTERN
|
||||
#include "jni_utils.h"
|
||||
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
bool acquire_jni_env(JavaVM *vm, JNIEnv **env)
|
||||
{
|
||||
int ret = vm->GetEnv((void**) env, JNI_VERSION_1_6);
|
||||
if (ret == JNI_EDETACHED)
|
||||
return vm->AttachCurrentThread(env, NULL) == 0;
|
||||
else
|
||||
return ret == JNI_OK;
|
||||
}
|
||||
|
||||
// Apparently it's considered slow to FindClass and GetMethodID every time we need them,
|
||||
// so let's have a nice cache here.
|
||||
|
||||
void init_methods_cache(JNIEnv *env)
|
||||
{
|
||||
static bool methods_initialized = false;
|
||||
if (methods_initialized)
|
||||
return;
|
||||
|
||||
#define FIND_CLASS(name) reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(name)))
|
||||
java_Integer = FIND_CLASS("java/lang/Integer");
|
||||
java_Integer_init = env->GetMethodID(java_Integer, "<init>", "(I)V");
|
||||
java_Double = FIND_CLASS("java/lang/Double");
|
||||
java_Double_init = env->GetMethodID(java_Double, "<init>", "(D)V");
|
||||
java_Boolean = FIND_CLASS("java/lang/Boolean");
|
||||
java_Boolean_init = env->GetMethodID(java_Boolean, "<init>", "(Z)V");
|
||||
|
||||
android_graphics_Bitmap = FIND_CLASS("android/graphics/Bitmap");
|
||||
// createBitmap(int[], int, int, android.graphics.Bitmap$Config)
|
||||
android_graphics_Bitmap_createBitmap = env->GetStaticMethodID(android_graphics_Bitmap, "createBitmap", "([IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
||||
android_graphics_Bitmap_Config = FIND_CLASS("android/graphics/Bitmap$Config");
|
||||
// static final android.graphics.Bitmap$Config ARGB_8888
|
||||
android_graphics_Bitmap_Config_ARGB_8888 = env->GetStaticFieldID(android_graphics_Bitmap_Config, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
||||
|
||||
mpv_MPVLib = FIND_CLASS("com/github/damontecres/wholphin/util/mpv/MPVLib");
|
||||
mpv_MPVLib_eventProperty_S = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;)V"); // eventProperty(String)
|
||||
mpv_MPVLib_eventProperty_Sb = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;Z)V"); // eventProperty(String, boolean)
|
||||
mpv_MPVLib_eventProperty_Sl = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;J)V"); // eventProperty(String, long)
|
||||
mpv_MPVLib_eventProperty_Sd = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;D)V"); // eventProperty(String, double)
|
||||
mpv_MPVLib_eventProperty_SS = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;Ljava/lang/String;)V"); // eventProperty(String, String)
|
||||
mpv_MPVLib_event = env->GetStaticMethodID(mpv_MPVLib, "event", "(I)V"); // event(int)
|
||||
mpv_MPVLib_end_file_event = env->GetStaticMethodID(mpv_MPVLib, "eventEndFile", "(II)V"); // eventEndFile(int, int)
|
||||
mpv_MPVLib_logMessage_SiS = env->GetStaticMethodID(mpv_MPVLib, "logMessage", "(Ljava/lang/String;ILjava/lang/String;)V"); // logMessage(String, int, String)
|
||||
#undef FIND_CLASS
|
||||
|
||||
methods_initialized = true;
|
||||
}
|
||||
30
app/src/main/jni/jni_utils.h
Normal file
30
app/src/main/jni/jni_utils.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#define jni_func_name(name) Java_com_github_damontecres_wholphin_util_mpv_MPVLib_##name
|
||||
#define jni_func(return_type, name, ...) JNIEXPORT return_type JNICALL jni_func_name(name) (JNIEnv *env, jobject obj, ##__VA_ARGS__)
|
||||
|
||||
bool acquire_jni_env(JavaVM *vm, JNIEnv **env);
|
||||
void init_methods_cache(JNIEnv *env);
|
||||
|
||||
#ifndef UTIL_EXTERN
|
||||
#define UTIL_EXTERN extern
|
||||
#endif
|
||||
|
||||
UTIL_EXTERN jclass java_Integer, java_Double, java_Boolean;
|
||||
UTIL_EXTERN jmethodID java_Integer_init, java_Double_init, java_Boolean_init;
|
||||
|
||||
UTIL_EXTERN jclass android_graphics_Bitmap, android_graphics_Bitmap_Config;
|
||||
UTIL_EXTERN jmethodID android_graphics_Bitmap_createBitmap;
|
||||
UTIL_EXTERN jfieldID android_graphics_Bitmap_Config_ARGB_8888;
|
||||
|
||||
UTIL_EXTERN jclass mpv_MPVLib;
|
||||
UTIL_EXTERN jmethodID mpv_MPVLib_eventProperty_S,
|
||||
mpv_MPVLib_eventProperty_Sb,
|
||||
mpv_MPVLib_eventProperty_Sl,
|
||||
mpv_MPVLib_eventProperty_Sd,
|
||||
mpv_MPVLib_eventProperty_SS,
|
||||
mpv_MPVLib_event,
|
||||
mpv_MPVLib_end_file_event,
|
||||
mpv_MPVLib_logMessage_SiS;
|
||||
9
app/src/main/jni/log.cpp
Normal file
9
app/src/main/jni/log.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#include "log.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void die(const char *msg)
|
||||
{
|
||||
ALOGE("%s", msg);
|
||||
exit(1);
|
||||
}
|
||||
20
app/src/main/jni/log.h
Normal file
20
app/src/main/jni/log.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
#define LOG_TAG "mpv"
|
||||
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#if DEBUG
|
||||
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
||||
#else
|
||||
#define ALOGV(...) (void)0
|
||||
#endif
|
||||
|
||||
__attribute__((noreturn)) void die(const char *msg);
|
||||
|
||||
#define CHECK_MPV_INIT() do { \
|
||||
if (__builtin_expect(!g_mpv, 0)) \
|
||||
die("libmpv is not initialized"); \
|
||||
} while (0)
|
||||
108
app/src/main/jni/main.cpp
Normal file
108
app/src/main/jni/main.cpp
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <locale.h>
|
||||
#include <atomic>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/jni.h>
|
||||
}
|
||||
|
||||
#include "log.h"
|
||||
#include "jni_utils.h"
|
||||
#include "event.h"
|
||||
|
||||
#define ARRAYLEN(a) (sizeof(a)/sizeof(a[0]))
|
||||
|
||||
extern "C" {
|
||||
jni_func(void, create, jobject appctx);
|
||||
jni_func(void, init);
|
||||
jni_func(void, destroy);
|
||||
|
||||
jni_func(void, command, jobjectArray jarray);
|
||||
};
|
||||
|
||||
JavaVM *g_vm;
|
||||
mpv_handle *g_mpv;
|
||||
std::atomic<bool> g_event_thread_request_exit(false);
|
||||
|
||||
static pthread_t event_thread_id;
|
||||
|
||||
static void prepare_environment(JNIEnv *env, jobject appctx) {
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
|
||||
if (!env->GetJavaVM(&g_vm) && g_vm)
|
||||
av_jni_set_java_vm(g_vm, NULL);
|
||||
|
||||
jobject global_appctx = env->NewGlobalRef(appctx);
|
||||
if (global_appctx)
|
||||
av_jni_set_android_app_ctx(global_appctx, NULL);
|
||||
|
||||
init_methods_cache(env);
|
||||
}
|
||||
|
||||
jni_func(void, create, jobject appctx) {
|
||||
prepare_environment(env, appctx);
|
||||
|
||||
if (g_mpv)
|
||||
die("mpv is already initialized");
|
||||
|
||||
g_mpv = mpv_create();
|
||||
if (!g_mpv)
|
||||
die("context init failed");
|
||||
|
||||
// use terminal log level but request verbose messages
|
||||
// this way --msg-level can be used to adjust later
|
||||
mpv_request_log_messages(g_mpv, "terminal-default");
|
||||
mpv_set_option_string(g_mpv, "msg-level", "all=v");
|
||||
}
|
||||
|
||||
jni_func(void, init) {
|
||||
if (!g_mpv)
|
||||
die("mpv is not created");
|
||||
|
||||
if (mpv_initialize(g_mpv) < 0)
|
||||
die("mpv init failed");
|
||||
|
||||
g_event_thread_request_exit = false;
|
||||
if (pthread_create(&event_thread_id, NULL, event_thread, NULL) != 0)
|
||||
die("thread create failed");
|
||||
pthread_setname_np(event_thread_id, "event_thread");
|
||||
}
|
||||
|
||||
jni_func(void, destroy) {
|
||||
if (!g_mpv) {
|
||||
ALOGV("mpv destroy called but it's already destroyed");
|
||||
return;
|
||||
}
|
||||
|
||||
// poke event thread and wait for it to exit
|
||||
g_event_thread_request_exit = true;
|
||||
mpv_wakeup(g_mpv);
|
||||
pthread_join(event_thread_id, NULL);
|
||||
|
||||
mpv_terminate_destroy(g_mpv);
|
||||
g_mpv = NULL;
|
||||
}
|
||||
|
||||
jni_func(void, command, jobjectArray jarray) {
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
const char *arguments[128] = {0};
|
||||
int len = env->GetArrayLength(jarray);
|
||||
if (len >= ARRAYLEN(arguments))
|
||||
die("too many command arguments");
|
||||
|
||||
for (int i = 0; i < len; ++i)
|
||||
arguments[i] = env->GetStringUTFChars((jstring)env->GetObjectArrayElement(jarray, i), NULL);
|
||||
|
||||
mpv_command(g_mpv, arguments);
|
||||
|
||||
for (int i = 0; i < len; ++i)
|
||||
env->ReleaseStringUTFChars((jstring)env->GetObjectArrayElement(jarray, i), arguments[i]);
|
||||
}
|
||||
125
app/src/main/jni/property.cpp
Normal file
125
app/src/main/jni/property.cpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
#include "jni_utils.h"
|
||||
#include "log.h"
|
||||
#include "globals.h"
|
||||
|
||||
extern "C" {
|
||||
jni_func(jint, setOptionString, jstring option, jstring value);
|
||||
|
||||
jni_func(jobject, getPropertyInt, jstring property);
|
||||
jni_func(void, setPropertyInt, jstring property, jint value);
|
||||
jni_func(jobject, getPropertyDouble, jstring property);
|
||||
jni_func(void, setPropertyDouble, jstring property, jdouble value);
|
||||
jni_func(jobject, getPropertyBoolean, jstring property);
|
||||
jni_func(void, setPropertyBoolean, jstring property, jboolean value);
|
||||
jni_func(jstring, getPropertyString, jstring jproperty);
|
||||
jni_func(void, setPropertyString, jstring jproperty, jstring jvalue);
|
||||
|
||||
jni_func(void, observeProperty, jstring property, jint format);
|
||||
}
|
||||
|
||||
jni_func(jint, setOptionString, jstring joption, jstring jvalue) {
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
const char *option = env->GetStringUTFChars(joption, NULL);
|
||||
const char *value = env->GetStringUTFChars(jvalue, NULL);
|
||||
|
||||
int result = mpv_set_option_string(g_mpv, option, value);
|
||||
|
||||
env->ReleaseStringUTFChars(joption, option);
|
||||
env->ReleaseStringUTFChars(jvalue, value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int common_get_property(JNIEnv *env, jstring jproperty, mpv_format format, void *output)
|
||||
{
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
const char *prop = env->GetStringUTFChars(jproperty, NULL);
|
||||
int result = mpv_get_property(g_mpv, prop, format, output);
|
||||
if (result == MPV_ERROR_PROPERTY_UNAVAILABLE)
|
||||
ALOGV("mpv_get_property(%s) format %d was unavailable", prop, format);
|
||||
else if (result < 0)
|
||||
ALOGE("mpv_get_property(%s) format %d returned error %s", prop, format, mpv_error_string(result));
|
||||
env->ReleaseStringUTFChars(jproperty, prop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int common_set_property(JNIEnv *env, jstring jproperty, mpv_format format, void *value)
|
||||
{
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
const char *prop = env->GetStringUTFChars(jproperty, NULL);
|
||||
int result = mpv_set_property(g_mpv, prop, format, value);
|
||||
if (result < 0)
|
||||
ALOGE("mpv_set_property(%s, %p) format %d returned error %s", prop, value, format, mpv_error_string(result));
|
||||
env->ReleaseStringUTFChars(jproperty, prop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
jni_func(jobject, getPropertyInt, jstring jproperty) {
|
||||
int64_t value = 0;
|
||||
if (common_get_property(env, jproperty, MPV_FORMAT_INT64, &value) < 0)
|
||||
return NULL;
|
||||
return env->NewObject(java_Integer, java_Integer_init, (jint)value);
|
||||
}
|
||||
|
||||
jni_func(jobject, getPropertyDouble, jstring jproperty) {
|
||||
double value = 0;
|
||||
if (common_get_property(env, jproperty, MPV_FORMAT_DOUBLE, &value) < 0)
|
||||
return NULL;
|
||||
return env->NewObject(java_Double, java_Double_init, (jdouble)value);
|
||||
}
|
||||
|
||||
jni_func(jobject, getPropertyBoolean, jstring jproperty) {
|
||||
int value = 0;
|
||||
if (common_get_property(env, jproperty, MPV_FORMAT_FLAG, &value) < 0)
|
||||
return NULL;
|
||||
return env->NewObject(java_Boolean, java_Boolean_init, (jboolean)value);
|
||||
}
|
||||
|
||||
jni_func(jstring, getPropertyString, jstring jproperty) {
|
||||
char *value;
|
||||
if (common_get_property(env, jproperty, MPV_FORMAT_STRING, &value) < 0)
|
||||
return NULL;
|
||||
jstring jvalue = env->NewStringUTF(value);
|
||||
mpv_free(value);
|
||||
return jvalue;
|
||||
}
|
||||
|
||||
jni_func(void, setPropertyInt, jstring jproperty, jint jvalue) {
|
||||
int64_t value = static_cast<int64_t>(jvalue);
|
||||
common_set_property(env, jproperty, MPV_FORMAT_INT64, &value);
|
||||
}
|
||||
|
||||
jni_func(void, setPropertyDouble, jstring jproperty, jdouble jvalue) {
|
||||
double value = static_cast<double>(jvalue);
|
||||
common_set_property(env, jproperty, MPV_FORMAT_DOUBLE, &value);
|
||||
}
|
||||
|
||||
jni_func(void, setPropertyBoolean, jstring jproperty, jboolean jvalue) {
|
||||
int value = jvalue == JNI_TRUE ? 1 : 0;
|
||||
common_set_property(env, jproperty, MPV_FORMAT_FLAG, &value);
|
||||
}
|
||||
|
||||
jni_func(void, setPropertyString, jstring jproperty, jstring jvalue) {
|
||||
const char *value = env->GetStringUTFChars(jvalue, NULL);
|
||||
common_set_property(env, jproperty, MPV_FORMAT_STRING, &value);
|
||||
env->ReleaseStringUTFChars(jvalue, value);
|
||||
}
|
||||
|
||||
jni_func(void, observeProperty, jstring property, jint format) {
|
||||
CHECK_MPV_INIT();
|
||||
const char *prop = env->GetStringUTFChars(property, NULL);
|
||||
int result = mpv_observe_property(g_mpv, 0, prop, (mpv_format)format);
|
||||
if (result < 0)
|
||||
ALOGE("mpv_observe_property(%s) format %d returned error %s", prop, format, mpv_error_string(result));
|
||||
env->ReleaseStringUTFChars(property, prop);
|
||||
}
|
||||
38
app/src/main/jni/render.cpp
Normal file
38
app/src/main/jni/render.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#include <jni.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
#include "jni_utils.h"
|
||||
#include "log.h"
|
||||
#include "globals.h"
|
||||
|
||||
extern "C" {
|
||||
jni_func(void, attachSurface, jobject surface_);
|
||||
jni_func(void, detachSurface);
|
||||
};
|
||||
|
||||
static jobject surface;
|
||||
|
||||
jni_func(void, attachSurface, jobject surface_) {
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
surface = env->NewGlobalRef(surface_);
|
||||
if (!surface)
|
||||
die("invalid surface provided");
|
||||
int64_t wid = reinterpret_cast<intptr_t>(surface);
|
||||
int result = mpv_set_option(g_mpv, "wid", MPV_FORMAT_INT64, &wid);
|
||||
if (result < 0)
|
||||
ALOGE("mpv_set_option(wid) returned error %s", mpv_error_string(result));
|
||||
}
|
||||
|
||||
jni_func(void, detachSurface) {
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
int64_t wid = 0;
|
||||
int result = mpv_set_option(g_mpv, "wid", MPV_FORMAT_INT64, &wid);
|
||||
if (result < 0)
|
||||
ALOGE("mpv_set_option(wid) returned error %s", mpv_error_string(result));
|
||||
|
||||
env->DeleteGlobalRef(surface);
|
||||
surface = NULL;
|
||||
}
|
||||
133
app/src/main/jni/thumbnail.cpp
Normal file
133
app/src/main/jni/thumbnail.cpp
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue