harmony: playback-lab: expose audio diagnostics

This commit is contained in:
BoatingCloud committed 2026-08-29 10:07:25 +08:00
1 parent 9941ad1da9
commit dfda10b6d8
5 files changed
+118 -2

No files matched your search

@@ -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)
@@ -1,5 +1,6 @@
#include "player.h"
#include "render.h"
#include "audio/out/ao_harmony.h"
#include <ace/xcomponent/native_interface_xcomponent.h>
#include <hilog/log.h>
@@ -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<double>(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},
+25 -1
View File
@@ -5,6 +5,7 @@
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstring>
#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<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);
}
break;
}
case MPV_EVENT_PROPERTY_CHANGE:
updateProperty(event->reply_userdata, static_cast<mpv_event_property*>(event->data));
break;
+1
View File
@@ -26,6 +26,7 @@ public:
~PlaybackEngine();
bool initialize();
bool setAudioEnabled(bool enabled);
void load(const std::string& url);
void play();
void pause();
@@ -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';
}
}