diff --git a/playback_lab/README.md b/playback_lab/README.md index db11b24401..ded67f88fe 100644 --- a/playback_lab/README.md +++ b/playback_lab/README.md @@ -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 摘要和回退原因。 diff --git a/playback_lab/entry/src/main/cpp/bridge.cpp b/playback_lab/entry/src/main/cpp/bridge.cpp index 752c08a79c..6964923dbc 100644 --- a/playback_lab/entry/src/main/cpp/bridge.cpp +++ b/playback_lab/entry/src/main/cpp/bridge.cpp @@ -1,6 +1,7 @@ #include "player.h" #include "render.h" #include "audio/out/ao_harmony.h" +#include "video/harmony_hwdec.h" #include #include @@ -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}, diff --git a/playback_lab/entry/src/main/cpp/player.cpp b/playback_lab/entry/src/main/cpp/player.cpp index c8eaa29693..0cf5073b78 100644 --- a/playback_lab/entry/src/main/cpp/player.cpp +++ b/playback_lab/entry/src/main/cpp/player.cpp @@ -1,5 +1,7 @@ #include "player.h" +#include "video/harmony_hwdec.h" + #include #include @@ -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 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 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(elapsed).count(); @@ -233,9 +270,60 @@ void PlaybackEngine::markFrameRendered() PlaybackSnapshot PlaybackEngine::snapshot() const { std::lock_guard lock(mutex_); + const_cast(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 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 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 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(property->data); break; + case kHwdecCurrent: + if (property->format == MPV_FORMAT_STRING) { + const char *value = *static_cast(property->data); + snapshot_.hwdecActive = value && std::strcmp(value, "no") != 0; + } + break; + case kHwdecInterop: + if (property->format == MPV_FORMAT_STRING) { + const char *value = *static_cast(property->data); + snapshot_.hwdecBackend = value ? value : ""; + } + break; + case kDecoder: + if (property->format == MPV_FORMAT_STRING) { + const char *value = *static_cast(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(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: diff --git a/playback_lab/entry/src/main/cpp/player.h b/playback_lab/entry/src/main/cpp/player.h index 8f2070f063..3edbee2836 100644 --- a/playback_lab/entry/src/main/cpp/player.h +++ b/playback_lab/entry/src/main/cpp/player.h @@ -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 stopEvents_{false}; std::atomic 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; }; diff --git a/playback_lab/entry/src/main/ets/pages/Index.ets b/playback_lab/entry/src/main/ets/pages/Index.ets index ceb529619d..7d49f3f725 100644 --- a/playback_lab/entry/src/main/ets/pages/Index.ets +++ b/playback_lab/entry/src/main/ets/pages/Index.ets @@ -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')) diff --git a/scripts/verify-libmpv-symbols.sh b/scripts/verify-libmpv-symbols.sh index 6063a6c643..9315bec620 100755 --- a/scripts/verify-libmpv-symbols.sh +++ b/scripts/verify-libmpv-symbols.sh @@ -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