harmony: playback-lab: expose audio diagnostics
This commit is contained in:
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},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user