H.264 High 10 on Android TV boxes without a 10-bit hardware decoder (Amlogic S905X4 class: onn 4K Pro, Homatics Box R) started black for over ten seconds and then played choppy: mpv tried MediaCodec first, hit "Could not initialize video chain", and only then fell back to software decode on the GL vo, where the Mali-G31 driver has no GL_EXT_texture_norm16 and pays an integer-texture conversion pass plus lanczos scaling it cannot afford at 1080p. Decide the decoder before mpv creates it. An on_preloaded hook now carries both per-file policies (Dolby Vision P5 reshaping and this one): when the track's codec-profile is High 10 (published at demux by the fork, see the mpv-build pin) and MediaCodecList advertises no hardware AVCProfileHigh10, hwdec is held at `no` and the session goes to the GL vo directly. The hold parks any hwdec write from Dart and restores it for the next file, as the DV P5 hold already did. On a GL vo whose driver lacks norm16 (probed once through a pbuffer EGL context), scale/cscale/dscale drop to bilinear and dither to off for the session, restored when the vo goes back to the plane. Only options still at their mpv defaults are touched, so a user's mpv.conf wins. Paired with the fork's rg8-backed 16-bit plane emulation, the box goes from 13 drops/s and a 13.7 s first frame to 0.7 drops/s and 2.5 s. Devices with norm16 (Shield, Pixel 7) take neither the tier nor the emulation. The native pin moves to edde746/mpv-build@0601b034da, which also picks up the earlier android/linux/windows patch-series squash. close #2065
146 lines
5.4 KiB
C++
146 lines
5.4 KiB
C++
#include <jni.h>
|
|
#include <mpv/client.h>
|
|
|
|
#include <cmath>
|
|
|
|
#include "globals.h"
|
|
#include "jni_utils.h"
|
|
#include "log.h"
|
|
|
|
static void sendPropertyUpdateToJava(JNIEnv* env, mpv_event_property* prop, int64_t source_id, bool has_source_id) {
|
|
jstring jprop = new_java_string(env, prop->name);
|
|
jstring jvalue = NULL;
|
|
const jboolean jhas_source_id = has_source_id ? JNI_TRUE : JNI_FALSE;
|
|
switch (prop->format) {
|
|
case MPV_FORMAT_NONE:
|
|
env->CallStaticVoidMethod(
|
|
mpv_MpvPlayer, mpv_MpvPlayer_onPropertyChanged_SJZ, jprop, (jlong)source_id, jhas_source_id);
|
|
break;
|
|
case MPV_FORMAT_FLAG:
|
|
env->CallStaticVoidMethod(
|
|
mpv_MpvPlayer, mpv_MpvPlayer_onPropertyChanged_SZJZ, jprop, (jboolean) * (int*)prop->data, (jlong)source_id,
|
|
jhas_source_id);
|
|
break;
|
|
case MPV_FORMAT_INT64:
|
|
env->CallStaticVoidMethod(
|
|
mpv_MpvPlayer, mpv_MpvPlayer_onPropertyChanged_SJJZ, jprop, (jlong) * (int64_t*)prop->data, (jlong)source_id,
|
|
jhas_source_id);
|
|
break;
|
|
case MPV_FORMAT_DOUBLE:
|
|
env->CallStaticVoidMethod(
|
|
mpv_MpvPlayer, mpv_MpvPlayer_onPropertyChanged_SDJZ, jprop, (jdouble) * (double*)prop->data, (jlong)source_id,
|
|
jhas_source_id);
|
|
break;
|
|
case MPV_FORMAT_STRING:
|
|
jvalue = new_java_string(env, *(const char**)prop->data);
|
|
env->CallStaticVoidMethod(
|
|
mpv_MpvPlayer, mpv_MpvPlayer_onPropertyChanged_SSJZ, jprop, jvalue, (jlong)source_id, jhas_source_id);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (jprop) env->DeleteLocalRef(jprop);
|
|
if (jvalue) env->DeleteLocalRef(jvalue);
|
|
}
|
|
|
|
static void sendEventToJava(
|
|
JNIEnv* env, int event, int64_t source_id, bool has_source_id, double position_seconds = 0.0,
|
|
bool has_position_seconds = false) {
|
|
env->CallStaticVoidMethod(
|
|
mpv_MpvPlayer, mpv_MpvPlayer_onEvent, (jint)event, (jlong)source_id, has_source_id ? JNI_TRUE : JNI_FALSE,
|
|
(jdouble)position_seconds, has_position_seconds ? JNI_TRUE : JNI_FALSE);
|
|
}
|
|
|
|
static void sendEndFileToJava(JNIEnv* env, mpv_event* event) {
|
|
mpv_event_end_file* end_file = (mpv_event_end_file*)event->data;
|
|
const int reason = end_file ? end_file->reason : -1;
|
|
const int64_t source_id = end_file ? end_file->playlist_entry_id : 0;
|
|
env->CallStaticVoidMethod(
|
|
mpv_MpvPlayer, mpv_MpvPlayer_onEndFile, (jint)reason, (jlong)source_id, end_file ? JNI_TRUE : JNI_FALSE);
|
|
}
|
|
|
|
static void sendLogMessageToJava(JNIEnv* env, mpv_event_log_message* msg) {
|
|
jstring jprefix = new_java_string(env, msg->prefix);
|
|
jstring jtext = new_java_string(env, msg->text);
|
|
|
|
env->CallStaticVoidMethod(mpv_MpvPlayer, mpv_MpvPlayer_onLogMessage, jprefix, (jint)msg->log_level, jtext);
|
|
|
|
if (jprefix) env->DeleteLocalRef(jprefix);
|
|
if (jtext) env->DeleteLocalRef(jtext);
|
|
}
|
|
|
|
// A hook holds mpv (playback does not proceed) until Kotlin answers with
|
|
// nativeHookContinue(id); MpvPlayer guarantees that answer for every hook.
|
|
static void sendHookToJava(JNIEnv* env, mpv_event_hook* hook) {
|
|
jstring jname = new_java_string(env, hook->name);
|
|
env->CallStaticVoidMethod(mpv_MpvPlayer, mpv_MpvPlayer_onHook, jname, (jlong)hook->id);
|
|
if (jname) env->DeleteLocalRef(jname);
|
|
}
|
|
|
|
void* event_thread(void* arg) {
|
|
JNIEnv* env = NULL;
|
|
acquire_jni_env(g_vm, &env);
|
|
if (!env) die("failed to acquire java env");
|
|
|
|
int64_t source_id = 0;
|
|
bool has_source_id = false;
|
|
|
|
while (true) {
|
|
mpv_event* mp_event;
|
|
mpv_event_property* mp_property;
|
|
mpv_event_log_message* msg;
|
|
|
|
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, source_id, has_source_id);
|
|
break;
|
|
case MPV_EVENT_END_FILE:
|
|
sendEndFileToJava(env, mp_event);
|
|
break;
|
|
case MPV_EVENT_START_FILE: {
|
|
mpv_event_start_file* start_file = (mpv_event_start_file*)mp_event->data;
|
|
has_source_id = start_file != NULL;
|
|
source_id = start_file ? start_file->playlist_entry_id : 0;
|
|
ALOGV("event: %s\n", mpv_event_name(mp_event->event_id));
|
|
sendEventToJava(env, mp_event->event_id, source_id, has_source_id);
|
|
break;
|
|
}
|
|
case MPV_EVENT_FILE_LOADED:
|
|
ALOGV("event: %s\n", mpv_event_name(mp_event->event_id));
|
|
sendEventToJava(env, mp_event->event_id, source_id, has_source_id);
|
|
break;
|
|
case MPV_EVENT_HOOK:
|
|
sendHookToJava(env, (mpv_event_hook*)mp_event->data);
|
|
break;
|
|
case MPV_EVENT_PLAYBACK_RESTART: {
|
|
double position_seconds = 0.0;
|
|
const bool has_position_seconds =
|
|
mpv_get_property(g_mpv, "time-pos", MPV_FORMAT_DOUBLE, &position_seconds) >= 0 &&
|
|
std::isfinite(position_seconds);
|
|
ALOGV("event: %s\n", mpv_event_name(mp_event->event_id));
|
|
sendEventToJava(env, mp_event->event_id, source_id, has_source_id, position_seconds, has_position_seconds);
|
|
break;
|
|
}
|
|
default:
|
|
// Nothing on the Kotlin side consumes the remaining ids (MpvEvent.fromId).
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_vm->DetachCurrentThread();
|
|
|
|
return NULL;
|
|
}
|