diff --git a/playback_lab/entry/src/main/cpp/CMakeLists.txt b/playback_lab/entry/src/main/cpp/CMakeLists.txt index 9399981fbd..b914a91846 100644 --- a/playback_lab/entry/src/main/cpp/CMakeLists.txt +++ b/playback_lab/entry/src/main/cpp/CMakeLists.txt @@ -10,6 +10,7 @@ if(NOT EXISTS "${MPV_LIBRARY}") endif() include_directories(${MPV_INCLUDE_DIR}) +include_directories(${REPO_ROOT}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_library(mpv SHARED IMPORTED) diff --git a/playback_lab/entry/src/main/cpp/bridge.cpp b/playback_lab/entry/src/main/cpp/bridge.cpp index 64fcbfec34..752c08a79c 100644 --- a/playback_lab/entry/src/main/cpp/bridge.cpp +++ b/playback_lab/entry/src/main/cpp/bridge.cpp @@ -1,5 +1,6 @@ #include "player.h" #include "render.h" +#include "audio/out/ao_harmony.h" #include #include @@ -74,6 +75,21 @@ napi_value play(napi_env env, napi_callback_info) return undefined(env); } +napi_value setAudioEnabled(napi_env env, napi_callback_info info) +{ + size_t argc = 1; + napi_value argv[1] = {nullptr}; + bool enabled = true; + 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.setAudioEnabled(enabled), &result); + return result; +} + napi_value pause(napi_env env, napi_callback_info) { gPlayer.pause(); @@ -130,6 +146,34 @@ napi_value getState(napi_env env, napi_callback_info) napi_set_named_property(env, object, "egl", value); napi_create_string_utf8(env, gRenderer.mpvStatus(), NAPI_AUTO_LENGTH, &value); napi_set_named_property(env, object, "mpv", value); + mpv_harmony_audio_stats audio = {}; + bool audioAvailable = mpv_harmony_audio_get_stats(&audio) != 0; + napi_get_boolean(env, audioAvailable, &value); + napi_set_named_property(env, object, "audioAvailable", value); + napi_create_string_utf8(env, audioAvailable ? "harmony" : "disabled", NAPI_AUTO_LENGTH, &value); + napi_set_named_property(env, object, "audioBackend", value); + napi_create_int32(env, audio.sample_rate, &value); + napi_set_named_property(env, object, "sampleRate", value); + napi_create_int32(env, audio.sample_format, &value); + napi_set_named_property(env, object, "sampleFormat", value); + napi_create_int32(env, audio.channels, &value); + napi_set_named_property(env, object, "channels", value); + napi_create_double(env, static_cast(audio.channel_layout), &value); + napi_set_named_property(env, object, "channelLayout", value); + napi_create_int64(env, audio.submitted_frames, &value); + napi_set_named_property(env, object, "submittedFrames", value); + napi_create_int64(env, audio.played_frames, &value); + napi_set_named_property(env, object, "playedFrames", value); + napi_create_int64(env, audio.estimated_delay_ms, &value); + napi_set_named_property(env, object, "estimatedAudioDelayMs", value); + napi_create_int64(env, audio.buffer_starvation, &value); + napi_set_named_property(env, object, "audioBufferStarvation", value); + napi_create_int64(env, audio.audio_flush_count, &value); + napi_set_named_property(env, object, "audioFlushCount", value); + napi_create_int64(env, audio.seek_audio_reset_count, &value); + napi_set_named_property(env, object, "seekAudioResetCount", value); + napi_create_int32(env, audio.state, &value); + napi_set_named_property(env, object, "audioState", value); return object; } @@ -150,6 +194,7 @@ napi_value Init(napi_env env, napi_value exports) napi_property_descriptor properties[] = { {"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}, {"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 5bd6a52964..c8eaa29693 100644 --- a/playback_lab/entry/src/main/cpp/player.cpp +++ b/playback_lab/entry/src/main/cpp/player.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #undef LOG_DOMAIN #undef LOG_TAG @@ -47,7 +48,7 @@ bool PlaybackEngine::initialize() {"terminal", "no"}, {"config", "no"}, {"hwdec", "no"}, - {"audio", "no"}, + {"aid", "auto"}, {"video-timing-offset", "0"}, {nullptr, nullptr}, }; @@ -69,6 +70,7 @@ bool PlaybackEngine::initialize() handle_ = nullptr; return false; } + mpv_request_log_messages(handle_, "debug"); observeProperties(); initialized_.store(true); stopEvents_.store(false); @@ -79,6 +81,19 @@ bool PlaybackEngine::initialize() return true; } +bool PlaybackEngine::setAudioEnabled(bool enabled) +{ + if (handle_ == nullptr) + return false; + int code = mpv_set_property_string(handle_, "aid", enabled ? "auto" : "no"); + if (code < 0) { + logMpvError("audio", code); + setError("audio toggle failed"); + return false; + } + return true; +} + void PlaybackEngine::observeProperties() { mpv_observe_property(handle_, kTimePos, "time-pos", MPV_FORMAT_DOUBLE); @@ -271,6 +286,15 @@ void PlaybackEngine::eventLoop() continue; } switch (event->event_id) { + 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); + } + break; + } case MPV_EVENT_PROPERTY_CHANGE: updateProperty(event->reply_userdata, static_cast(event->data)); break; diff --git a/playback_lab/entry/src/main/cpp/player.h b/playback_lab/entry/src/main/cpp/player.h index 71cb384aa4..8f2070f063 100644 --- a/playback_lab/entry/src/main/cpp/player.h +++ b/playback_lab/entry/src/main/cpp/player.h @@ -26,6 +26,7 @@ public: ~PlaybackEngine(); bool initialize(); + bool setAudioEnabled(bool enabled); void load(const std::string& url); void play(); void pause(); diff --git a/playback_lab/entry/src/main/ets/pages/Index.ets b/playback_lab/entry/src/main/ets/pages/Index.ets index f08d62eea3..ceb529619d 100644 --- a/playback_lab/entry/src/main/ets/pages/Index.ets +++ b/playback_lab/entry/src/main/ets/pages/Index.ets @@ -12,6 +12,19 @@ interface PlaybackState { surface: string; egl: string; mpv: string; + audioAvailable: boolean; + audioBackend: string; + sampleRate: number; + sampleFormat: number; + channels: number; + channelLayout: number; + submittedFrames: number; + playedFrames: number; + estimatedAudioDelayMs: number; + audioBufferStarvation: number; + audioFlushCount: number; + seekAudioResetCount: number; + audioState: number; } const LOCAL_TEST_URL: string = 'file:///data/storage/el2/base/haps/entry/files/sample.mp4'; @@ -21,10 +34,14 @@ const LOCAL_TEST_URL: string = 'file:///data/storage/el2/base/haps/entry/files/s struct Index { @State url: string = LOCAL_TEST_URL; @State status: string = 'IDLE'; + @State audioEnabled: boolean = true; @State detail: string = 'Surface waiting'; @State state: PlaybackState = { state: 'IDLE', position: 0, duration: 0, fps: 0, dropped: 0, - firstFrameMs: -1, renderCount: 0, lastError: '', surface: 'WAITING', egl: 'WAITING', mpv: 'WAITING' + 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 }; private poller: number = 0; @@ -59,6 +76,10 @@ struct Index { playbackLab.load(LOCAL_TEST_URL); }) Button('Load URL').onClick(() => playbackLab.load(this.url)) + Button(this.audioEnabled ? 'Audio On' : 'Audio Off').onClick(() => { + const enabled: boolean = !this.audioEnabled; + if (playbackLab.setAudioEnabled(enabled) as boolean) this.audioEnabled = enabled; + }) }.width('100%') Row({ space: 8 }) { Button('Play').onClick(() => playbackLab.play()) @@ -74,6 +95,12 @@ struct Index { .fontSize(13).fontColor($r('app.color.muted')).width('100%') Text(`dropped=${this.state.dropped} firstFrameMs=${this.state.firstFrameMs} renderCount=${this.state.renderCount}`) .fontSize(13).fontColor($r('app.color.muted')).width('100%') + Text(`audio=${this.state.audioBackend} state=${audioStateLabel(this.state.audioState)} rate=${this.state.sampleRate} format=${audioFormatLabel(this.state.sampleFormat)} channels=${this.state.channels}`) + .fontSize(13).fontColor($r('app.color.muted')).width('100%').maxLines(2) + Text(`submitted=${this.state.submittedFrames} played=${this.state.playedFrames} delay=${this.state.estimatedAudioDelayMs}ms starvation=${this.state.audioBufferStarvation}`) + .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(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')) @@ -83,3 +110,21 @@ struct Index { function detailLabel(value: string): string { return `detail: ${value}`; } + +function audioFormatLabel(value: number): string { + if (value === 5) return 'float32'; + if (value === 2) return 's16'; + return 'n/a'; +} + +function audioStateLabel(value: number): string { + switch (value) { + case 1: return 'READY'; + case 2: return 'RUNNING'; + case 3: return 'PAUSED'; + case 4: return 'STOPPED'; + case 5: return 'ERROR'; + case 6: return 'RELEASED'; + default: return 'DISABLED'; + } +}