feat: surface OHCodec diagnostics in Playback Lab
This commit is contained in:
1 parent
3f6132637b
commit
37df87fc56
6 files changed
+213
-7
No files matched your search
@@ -10,3 +10,5 @@
|
||||
```
|
||||
|
||||
`stage-mpv.sh` 只把本地 Phase 1 产物复制到未跟踪的 HAP native 库目录,不把 34 MB 的二进制提交到仓库。
|
||||
|
||||
面板中的 `OHCodec Probe` 只请求 `hwdec=ohcodec`,不会把当前 EGL window 注入 Direct Surface。没有独占 `ohos_direct_surface` 时,MPV 必须回退软件解码;面板会显示 requested/actual decoder、hwdec state、capability 摘要和回退原因。
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "player.h"
|
||||
#include "render.h"
|
||||
#include "audio/out/ao_harmony.h"
|
||||
#include "video/harmony_hwdec.h"
|
||||
|
||||
#include <ace/xcomponent/native_interface_xcomponent.h>
|
||||
#include <hilog/log.h>
|
||||
@@ -90,6 +91,20 @@ napi_value setAudioEnabled(napi_env env, napi_callback_info info)
|
||||
return result;
|
||||
}
|
||||
|
||||
napi_value setHardwareDecode(napi_env env, napi_callback_info info)
|
||||
{
|
||||
size_t argc = 1;
|
||||
napi_value argv[1] = {nullptr};
|
||||
bool enabled = false;
|
||||
bool value = false;
|
||||
if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) == napi_ok && argc > 0 &&
|
||||
napi_get_value_bool(env, argv[0], &value) == napi_ok)
|
||||
enabled = value;
|
||||
napi_value result = nullptr;
|
||||
napi_get_boolean(env, gPlayer.setHardwareDecode(enabled), &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
napi_value pause(napi_env env, napi_callback_info)
|
||||
{
|
||||
gPlayer.pause();
|
||||
@@ -140,6 +155,34 @@ napi_value getState(napi_env env, napi_callback_info)
|
||||
napi_set_named_property(env, object, "renderCount", value);
|
||||
napi_create_string_utf8(env, state.lastError.c_str(), NAPI_AUTO_LENGTH, &value);
|
||||
napi_set_named_property(env, object, "lastError", value);
|
||||
napi_create_string_utf8(env, state.requestedDecoder.c_str(), NAPI_AUTO_LENGTH, &value);
|
||||
napi_set_named_property(env, object, "requestedDecoder", value);
|
||||
napi_create_string_utf8(env, state.actualDecoder.c_str(), NAPI_AUTO_LENGTH, &value);
|
||||
napi_set_named_property(env, object, "actualDecoder", value);
|
||||
napi_get_boolean(env, state.hwdecRequested, &value);
|
||||
napi_set_named_property(env, object, "hwdecRequested", value);
|
||||
napi_get_boolean(env, state.hwdecActive, &value);
|
||||
napi_set_named_property(env, object, "hwdecActive", value);
|
||||
napi_create_string_utf8(env, state.hwdecBackend.c_str(), NAPI_AUTO_LENGTH, &value);
|
||||
napi_set_named_property(env, object, "hwdecBackend", value);
|
||||
napi_create_string_utf8(env, state.hwFallbackReason.c_str(), NAPI_AUTO_LENGTH, &value);
|
||||
napi_set_named_property(env, object, "hwdecFallbackReason", value);
|
||||
napi_create_string_utf8(env, state.hwCapability.c_str(), NAPI_AUTO_LENGTH, &value);
|
||||
napi_set_named_property(env, object, "hwCapability", value);
|
||||
napi_create_int64(env, state.hardwareFrameCount, &value);
|
||||
napi_set_named_property(env, object, "hardwareFrameCount", value);
|
||||
napi_create_int64(env, state.softwareFrameCount, &value);
|
||||
napi_set_named_property(env, object, "softwareFrameCount", value);
|
||||
napi_create_int64(env, state.hwFallbackCount, &value);
|
||||
napi_set_named_property(env, object, "hwFallbackCount", value);
|
||||
napi_create_int64(env, state.nativeBufferImportCount, &value);
|
||||
napi_set_named_property(env, object, "nativeBufferImportCount", value);
|
||||
napi_create_int64(env, state.nativeBufferImportFailure, &value);
|
||||
napi_set_named_property(env, object, "nativeBufferImportFailure", value);
|
||||
napi_create_int64(env, state.surfacePresentCount, &value);
|
||||
napi_set_named_property(env, object, "surfacePresentCount", value);
|
||||
napi_create_int64(env, state.gpuInteropFrameCount, &value);
|
||||
napi_set_named_property(env, object, "gpuInteropFrameCount", value);
|
||||
napi_create_string_utf8(env, gRenderer.surfaceStatus(), NAPI_AUTO_LENGTH, &value);
|
||||
napi_set_named_property(env, object, "surface", value);
|
||||
napi_create_string_utf8(env, gRenderer.eglStatus(), NAPI_AUTO_LENGTH, &value);
|
||||
@@ -195,6 +238,7 @@ napi_value Init(napi_env env, napi_value exports)
|
||||
{"load", nullptr, load, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||
{"play", nullptr, play, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||
{"setAudioEnabled", nullptr, setAudioEnabled, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||
{"setHardwareDecode", nullptr, setHardwareDecode, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||
{"pause", nullptr, pause, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||
{"seek", nullptr, seek, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||
{"stop", nullptr, stop, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "player.h"
|
||||
|
||||
#include "video/harmony_hwdec.h"
|
||||
|
||||
#include <hilog/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -26,7 +28,10 @@ void logMpvError(const char* operation, int code)
|
||||
}
|
||||
}
|
||||
|
||||
PlaybackEngine::PlaybackEngine() = default;
|
||||
PlaybackEngine::PlaybackEngine()
|
||||
{
|
||||
mpv_harmony_hwdec_set_requested(0);
|
||||
}
|
||||
|
||||
PlaybackEngine::~PlaybackEngine()
|
||||
{
|
||||
@@ -94,6 +99,30 @@ bool PlaybackEngine::setAudioEnabled(bool enabled)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlaybackEngine::setHardwareDecode(bool enabled)
|
||||
{
|
||||
if (handle_ == nullptr)
|
||||
return false;
|
||||
int code = mpv_set_property_string(handle_, "hwdec", enabled ? "ohcodec" : "no");
|
||||
if (code < 0) {
|
||||
logMpvError("hwdec", code);
|
||||
setError("hardware decode toggle failed");
|
||||
return false;
|
||||
}
|
||||
mpv_harmony_hwdec_set_requested(enabled ? 1 : 0);
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
snapshot_.hwdecRequested = enabled;
|
||||
snapshot_.requestedDecoder = enabled ? "ohcodec" : "software";
|
||||
if (!enabled) {
|
||||
snapshot_.hwdecActive = false;
|
||||
snapshot_.actualDecoder = "software";
|
||||
} else {
|
||||
snapshot_.actualDecoder = "pending";
|
||||
snapshot_.hwFallbackReason.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlaybackEngine::observeProperties()
|
||||
{
|
||||
mpv_observe_property(handle_, kTimePos, "time-pos", MPV_FORMAT_DOUBLE);
|
||||
@@ -101,6 +130,9 @@ void PlaybackEngine::observeProperties()
|
||||
mpv_observe_property(handle_, kPause, "pause", MPV_FORMAT_FLAG);
|
||||
mpv_observe_property(handle_, kFps, "estimated-vf-fps", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(handle_, kDropped, "vo-drop-frame-count", MPV_FORMAT_INT64);
|
||||
mpv_observe_property(handle_, kHwdecCurrent, "hwdec-current", MPV_FORMAT_STRING);
|
||||
mpv_observe_property(handle_, kHwdecInterop, "hwdec-interop", MPV_FORMAT_STRING);
|
||||
mpv_observe_property(handle_, kDecoder, "decoder", MPV_FORMAT_STRING);
|
||||
}
|
||||
|
||||
void PlaybackEngine::load(const std::string& url)
|
||||
@@ -215,13 +247,18 @@ void PlaybackEngine::release()
|
||||
mpv_terminate_destroy(handle_);
|
||||
handle_ = nullptr;
|
||||
}
|
||||
mpv_harmony_hwdec_set_requested(0);
|
||||
setState("RELEASED");
|
||||
}
|
||||
|
||||
void PlaybackEngine::markFrameRendered()
|
||||
{
|
||||
bool hwdec_active = false;
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
hwdec_active = snapshot_.hwdecActive;
|
||||
++snapshot_.renderCount;
|
||||
if (!hwdec_active)
|
||||
mpv_harmony_hwdec_record_software_frame();
|
||||
if (snapshot_.firstFrameMs < 0) {
|
||||
auto elapsed = std::chrono::steady_clock::now() - startedAt_;
|
||||
snapshot_.firstFrameMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
|
||||
@@ -233,9 +270,60 @@ void PlaybackEngine::markFrameRendered()
|
||||
PlaybackSnapshot PlaybackEngine::snapshot() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
const_cast<PlaybackEngine *>(this)->syncHwdecStatsLocked();
|
||||
return snapshot_;
|
||||
}
|
||||
|
||||
void PlaybackEngine::syncHwdecStatsLocked()
|
||||
{
|
||||
mpv_harmony_hwdec_stats stats = {};
|
||||
if (!mpv_harmony_hwdec_get_stats(&stats))
|
||||
return;
|
||||
snapshot_.hwdecRequested = stats.hwdec_requested != 0;
|
||||
snapshot_.hwdecActive = stats.hwdec_active != 0;
|
||||
snapshot_.hardwareFrameCount = stats.hardware_frame_count;
|
||||
snapshot_.softwareFrameCount = stats.software_frame_count;
|
||||
snapshot_.hwFallbackCount = stats.hw_fallback_count;
|
||||
snapshot_.nativeBufferImportCount = stats.native_buffer_import_count;
|
||||
snapshot_.nativeBufferImportFailure = stats.native_buffer_import_failure;
|
||||
snapshot_.surfacePresentCount = stats.surface_present_count;
|
||||
snapshot_.gpuInteropFrameCount = stats.gpu_interop_frame_count;
|
||||
snapshot_.hwdecBackend = stats.hwdec_backend;
|
||||
snapshot_.hwFallbackReason = stats.fallback_reason;
|
||||
snapshot_.hwCapability = stats.capability_summary;
|
||||
if (stats.decoder_name[0] != '\0')
|
||||
snapshot_.actualDecoder = stats.decoder_name;
|
||||
else if (snapshot_.hwdecRequested && !snapshot_.hwdecActive && stats.hw_fallback_count > 0)
|
||||
snapshot_.actualDecoder = "software";
|
||||
}
|
||||
|
||||
void PlaybackEngine::handleLogMessage(const mpv_event_log_message* message)
|
||||
{
|
||||
if (!message || !message->text)
|
||||
return;
|
||||
const char* text = message->text;
|
||||
if (std::strstr(text, "h264_ohcodec") || std::strstr(text, "hevc_ohcodec")) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
snapshot_.actualDecoder = std::strstr(text, "hevc_ohcodec") ? "hevc_ohcodec" : "h264_ohcodec";
|
||||
snapshot_.hwdecActive = true;
|
||||
}
|
||||
if (message->prefix && std::strncmp(message->prefix, "vd", 2) == 0 &&
|
||||
(std::strstr(text, "fallback") || std::strstr(text, "falling back") ||
|
||||
std::strstr(text, "No hardware decoding"))) {
|
||||
bool requested = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
requested = snapshot_.hwdecRequested;
|
||||
}
|
||||
if (requested)
|
||||
mpv_harmony_hwdec_set_fallback(text);
|
||||
}
|
||||
if (message->prefix && std::strncmp(message->prefix, "vd", 2) == 0) {
|
||||
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG,
|
||||
"mpv[%{public}s] %{public}s", message->prefix, text);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaybackEngine::setState(const char* state)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
@@ -273,6 +361,27 @@ void PlaybackEngine::updateProperty(uint64_t userdata, const mpv_event_property*
|
||||
case kDropped:
|
||||
if (property->format == MPV_FORMAT_INT64) snapshot_.dropped = *static_cast<int64_t*>(property->data);
|
||||
break;
|
||||
case kHwdecCurrent:
|
||||
if (property->format == MPV_FORMAT_STRING) {
|
||||
const char *value = *static_cast<char **>(property->data);
|
||||
snapshot_.hwdecActive = value && std::strcmp(value, "no") != 0;
|
||||
}
|
||||
break;
|
||||
case kHwdecInterop:
|
||||
if (property->format == MPV_FORMAT_STRING) {
|
||||
const char *value = *static_cast<char **>(property->data);
|
||||
snapshot_.hwdecBackend = value ? value : "";
|
||||
}
|
||||
break;
|
||||
case kDecoder:
|
||||
if (property->format == MPV_FORMAT_STRING) {
|
||||
const char *value = *static_cast<char **>(property->data);
|
||||
if (value && value[0]) {
|
||||
snapshot_.actualDecoder = value;
|
||||
mpv_harmony_hwdec_set_decoder(value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -289,10 +398,9 @@ void PlaybackEngine::eventLoop()
|
||||
case MPV_EVENT_LOG_MESSAGE: {
|
||||
auto* message = static_cast<mpv_event_log_message*>(event->data);
|
||||
if (message != nullptr && message->prefix != nullptr &&
|
||||
std::strncmp(message->prefix, "ao", 2) == 0) {
|
||||
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG,
|
||||
"mpv[%{public}s] %{public}s", message->prefix, message->text);
|
||||
}
|
||||
(std::strncmp(message->prefix, "ao", 2) == 0 ||
|
||||
std::strncmp(message->prefix, "vd", 2) == 0))
|
||||
handleLogMessage(message);
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_PROPERTY_CHANGE:
|
||||
|
||||
@@ -18,6 +18,20 @@ struct PlaybackSnapshot {
|
||||
int64_t firstFrameMs = -1;
|
||||
uint64_t renderCount = 0;
|
||||
std::string lastError;
|
||||
std::string requestedDecoder = "software";
|
||||
std::string actualDecoder = "software";
|
||||
std::string hwdecBackend;
|
||||
std::string hwFallbackReason;
|
||||
std::string hwCapability;
|
||||
bool hwdecRequested = false;
|
||||
bool hwdecActive = false;
|
||||
int64_t hardwareFrameCount = 0;
|
||||
int64_t softwareFrameCount = 0;
|
||||
int64_t hwFallbackCount = 0;
|
||||
int64_t nativeBufferImportCount = 0;
|
||||
int64_t nativeBufferImportFailure = 0;
|
||||
int64_t surfacePresentCount = 0;
|
||||
int64_t gpuInteropFrameCount = 0;
|
||||
};
|
||||
|
||||
class PlaybackEngine {
|
||||
@@ -27,6 +41,7 @@ public:
|
||||
|
||||
bool initialize();
|
||||
bool setAudioEnabled(bool enabled);
|
||||
bool setHardwareDecode(bool enabled);
|
||||
void load(const std::string& url);
|
||||
void play();
|
||||
void pause();
|
||||
@@ -44,6 +59,8 @@ private:
|
||||
void setState(const char* state);
|
||||
void setError(const std::string& error);
|
||||
void updateProperty(uint64_t userdata, const mpv_event_property* property);
|
||||
void syncHwdecStatsLocked();
|
||||
void handleLogMessage(const mpv_event_log_message* message);
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
PlaybackSnapshot snapshot_;
|
||||
@@ -52,4 +69,7 @@ private:
|
||||
std::atomic<bool> stopEvents_{false};
|
||||
std::atomic<bool> initialized_{false};
|
||||
std::chrono::steady_clock::time_point startedAt_;
|
||||
static constexpr uint64_t kHwdecCurrent = 6;
|
||||
static constexpr uint64_t kHwdecInterop = 7;
|
||||
static constexpr uint64_t kDecoder = 8;
|
||||
};
|
||||
@@ -25,6 +25,20 @@ interface PlaybackState {
|
||||
audioFlushCount: number;
|
||||
seekAudioResetCount: number;
|
||||
audioState: number;
|
||||
requestedDecoder: string;
|
||||
actualDecoder: string;
|
||||
hwdecRequested: boolean;
|
||||
hwdecActive: boolean;
|
||||
hwdecBackend: string;
|
||||
hwdecFallbackReason: string;
|
||||
hwCapability: string;
|
||||
hardwareFrameCount: number;
|
||||
softwareFrameCount: number;
|
||||
hwFallbackCount: number;
|
||||
nativeBufferImportCount: number;
|
||||
nativeBufferImportFailure: number;
|
||||
surfacePresentCount: number;
|
||||
gpuInteropFrameCount: number;
|
||||
}
|
||||
|
||||
const LOCAL_TEST_URL: string = 'file:///data/storage/el2/base/haps/entry/files/sample.mp4';
|
||||
@@ -41,7 +55,12 @@ struct Index {
|
||||
firstFrameMs: -1, renderCount: 0, lastError: '', surface: 'WAITING', egl: 'WAITING', mpv: 'WAITING',
|
||||
audioAvailable: false, audioBackend: 'disabled', sampleRate: 0, sampleFormat: 0, channels: 0,
|
||||
channelLayout: 0, submittedFrames: 0, playedFrames: 0, estimatedAudioDelayMs: 0,
|
||||
audioBufferStarvation: 0, audioFlushCount: 0, seekAudioResetCount: 0, audioState: 0
|
||||
audioBufferStarvation: 0, audioFlushCount: 0, seekAudioResetCount: 0, audioState: 0,
|
||||
requestedDecoder: 'software', actualDecoder: 'software', hwdecRequested: false,
|
||||
hwdecActive: false, hwdecBackend: '', hwdecFallbackReason: '', hwCapability: '',
|
||||
hardwareFrameCount: 0, softwareFrameCount: 0, hwFallbackCount: 0,
|
||||
nativeBufferImportCount: 0, nativeBufferImportFailure: 0, surfacePresentCount: 0,
|
||||
gpuInteropFrameCount: 0
|
||||
};
|
||||
private poller: number = 0;
|
||||
|
||||
@@ -87,6 +106,12 @@ struct Index {
|
||||
Button('Stop').onClick(() => playbackLab.stop())
|
||||
Button('Release').onClick(() => playbackLab.release())
|
||||
}.width('100%')
|
||||
Row({ space: 8 }) {
|
||||
Button(this.state.hwdecRequested ? 'OHCodec On' : 'OHCodec Probe').onClick(() => {
|
||||
const enabled: boolean = !this.state.hwdecRequested;
|
||||
playbackLab.setHardwareDecode(enabled);
|
||||
})
|
||||
}.width('100%')
|
||||
Row({ space: 8 }) {
|
||||
Button('-10s').onClick(() => playbackLab.seek(-10))
|
||||
Button('+10s').onClick(() => playbackLab.seek(10))
|
||||
@@ -101,6 +126,12 @@ struct Index {
|
||||
.fontSize(13).fontColor($r('app.color.muted')).width('100%').maxLines(2)
|
||||
Text(`flushes=${this.state.audioFlushCount} seekResets=${this.state.seekAudioResetCount}`)
|
||||
.fontSize(13).fontColor($r('app.color.muted')).width('100%')
|
||||
Text(`decoder=${this.state.requestedDecoder} -> ${this.state.actualDecoder} requested=${this.state.hwdecRequested} active=${this.state.hwdecActive} backend=${this.state.hwdecBackend || 'none'}`)
|
||||
.fontSize(13).fontColor($r('app.color.muted')).width('100%').maxLines(2)
|
||||
Text(`hwFrames=${this.state.hardwareFrameCount} swFrames=${this.state.softwareFrameCount} fallbacks=${this.state.hwFallbackCount} surfacePresent=${this.state.surfacePresentCount} interop=${this.state.gpuInteropFrameCount}`)
|
||||
.fontSize(13).fontColor($r('app.color.muted')).width('100%').maxLines(2)
|
||||
Text(`fallback=${this.state.hwdecFallbackReason || 'none'} capability=${this.state.hwCapability || 'DEVICE TEST REQUIRED'}`)
|
||||
.fontSize(13).fontColor($r('app.color.muted')).width('100%').maxLines(3)
|
||||
Text(detailLabel(this.detail)).fontSize(13).fontColor($r('app.color.muted')).width('100%').maxLines(2)
|
||||
}
|
||||
.padding(16).width('100%').height('100%').backgroundColor($r('app.color.background'))
|
||||
|
||||
@@ -17,7 +17,8 @@ for symbol in \
|
||||
mpv_render_context_render \
|
||||
mpv_render_context_set_update_callback \
|
||||
mpv_render_context_free \
|
||||
mpv_harmony_audio_get_stats; do
|
||||
mpv_harmony_audio_get_stats \
|
||||
mpv_harmony_hwdec_get_stats; do
|
||||
printf '%s\n' "$SYMBOLS" | grep -Eq "[[:space:]]${symbol}$" || {
|
||||
printf 'Missing libmpv symbol: %s\n' "$symbol" >&2
|
||||
exit 1
|
||||
|
||||
Reference in new issue
Block a user