harmony: audio: add OHAudio output backend
This commit is contained in:
1 parent
c650a4214d
commit
7021f883cb
2 files changed
+495
No files matched your search
@@ -0,0 +1,446 @@
|
||||
/* HarmonyOS OHAudio output for the MPV pull-based audio contract. */
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/out/ao_harmony.h"
|
||||
#include "common/msg.h"
|
||||
#include "internal.h"
|
||||
#include "osdep/threads.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
#if HAVE_HARMONY_AUDIO
|
||||
|
||||
#include <ohaudio/native_audiostreambuilder.h>
|
||||
#include <ohaudio/native_audiorenderer.h>
|
||||
#include <multimedia/native_audio_channel_layout.h>
|
||||
|
||||
struct priv {
|
||||
OH_AudioRenderer *renderer;
|
||||
mp_thread clock_thread;
|
||||
bool clock_thread_valid;
|
||||
atomic_bool stop_clock;
|
||||
atomic_bool callback_enabled;
|
||||
atomic_int callbacks_inflight;
|
||||
_Atomic int64_t submitted_frames;
|
||||
_Atomic int64_t played_frames;
|
||||
_Atomic int64_t raw_played_frames;
|
||||
_Atomic int64_t timestamp_origin_raw;
|
||||
_Atomic int64_t audio_timestamp_ns;
|
||||
_Atomic int64_t estimated_delay_ms;
|
||||
_Atomic int64_t buffer_starvation;
|
||||
_Atomic int64_t callback_count;
|
||||
int32_t latency_ms;
|
||||
int32_t frame_size;
|
||||
int32_t channel_count;
|
||||
uint64_t channel_layout;
|
||||
};
|
||||
|
||||
static _Atomic int32_t g_state = MPV_HARMONY_AUDIO_DISABLED;
|
||||
static _Atomic int32_t g_sample_rate;
|
||||
static _Atomic int32_t g_sample_format;
|
||||
static _Atomic int32_t g_channels;
|
||||
static _Atomic uint64_t g_channel_layout;
|
||||
static _Atomic int64_t g_submitted_frames;
|
||||
static _Atomic int64_t g_played_frames;
|
||||
static _Atomic int64_t g_audio_timestamp_ns;
|
||||
static _Atomic int64_t g_estimated_delay_ms;
|
||||
static _Atomic int64_t g_buffer_starvation;
|
||||
static _Atomic int64_t g_system_underrun;
|
||||
static _Atomic int64_t g_callback_count;
|
||||
static _Atomic int64_t g_audio_flush_count;
|
||||
static _Atomic int64_t g_seek_audio_reset_count;
|
||||
|
||||
static void reset_stats(void)
|
||||
{
|
||||
atomic_store(&g_state, MPV_HARMONY_AUDIO_DISABLED);
|
||||
atomic_store(&g_sample_rate, 0);
|
||||
atomic_store(&g_sample_format, AF_FORMAT_UNKNOWN);
|
||||
atomic_store(&g_channels, 0);
|
||||
atomic_store(&g_channel_layout, 0);
|
||||
atomic_store(&g_submitted_frames, 0);
|
||||
atomic_store(&g_played_frames, 0);
|
||||
atomic_store(&g_audio_timestamp_ns, 0);
|
||||
atomic_store(&g_estimated_delay_ms, 0);
|
||||
atomic_store(&g_buffer_starvation, 0);
|
||||
atomic_store(&g_system_underrun, 0);
|
||||
atomic_store(&g_callback_count, 0);
|
||||
atomic_store(&g_audio_flush_count, 0);
|
||||
atomic_store(&g_seek_audio_reset_count, 0);
|
||||
}
|
||||
|
||||
static void set_state(int32_t state)
|
||||
{
|
||||
atomic_store(&g_state, state);
|
||||
}
|
||||
|
||||
static void log_result(struct ao *ao, const char *operation, OH_AudioStream_Result result)
|
||||
{
|
||||
MP_ERR(ao, "%s failed: %d\n", operation, result);
|
||||
}
|
||||
|
||||
static OH_AudioStream_SampleFormat sample_format_for(int format)
|
||||
{
|
||||
return af_fmt_is_float(format) ? AUDIOSTREAM_SAMPLE_F32LE : AUDIOSTREAM_SAMPLE_S16LE;
|
||||
}
|
||||
|
||||
static void fill_silence(struct ao *ao, void *data, int samples)
|
||||
{
|
||||
if (samples > 0)
|
||||
af_fill_silence(data, (size_t)samples * ao->sstride, ao->format);
|
||||
}
|
||||
|
||||
static OH_AudioData_Callback_Result write_data(OH_AudioRenderer *renderer, void *user_data,
|
||||
void *audio_data, int32_t audio_data_size)
|
||||
{
|
||||
(void)renderer;
|
||||
struct ao *ao = user_data;
|
||||
struct priv *p = ao->priv;
|
||||
atomic_fetch_add(&p->callbacks_inflight, 1);
|
||||
int64_t callback_number = atomic_fetch_add(&p->callback_count, 1) + 1;
|
||||
atomic_fetch_add(&g_callback_count, 1);
|
||||
|
||||
int stride = ao->sstride;
|
||||
if (audio_data == NULL || audio_data_size <= 0 || stride <= 0) {
|
||||
atomic_fetch_sub(&p->callbacks_inflight, 1);
|
||||
return AUDIO_DATA_CALLBACK_RESULT_INVALID;
|
||||
}
|
||||
|
||||
int samples = audio_data_size / stride;
|
||||
int remainder = audio_data_size - samples * stride;
|
||||
if (!atomic_load(&p->callback_enabled) || samples <= 0) {
|
||||
memset(audio_data, 0, (size_t)audio_data_size);
|
||||
atomic_fetch_sub(&p->callbacks_inflight, 1);
|
||||
return AUDIO_DATA_CALLBACK_RESULT_VALID;
|
||||
}
|
||||
|
||||
if (callback_number == 1)
|
||||
MP_VERBOSE(ao, "OHAudio first callback: %d frames, %d bytes\n", samples, audio_data_size);
|
||||
|
||||
void *planes[1] = {audio_data};
|
||||
int64_t end_time = mp_time_ns() + MP_TIME_MS_TO_NS(p->latency_ms);
|
||||
end_time += MP_TIME_S_TO_NS(samples / (double)ao->samplerate);
|
||||
int got = ao_read_data(ao, planes, samples, end_time, NULL, true, false);
|
||||
if (got < samples) {
|
||||
fill_silence(ao, (char *)audio_data + (size_t)got * stride, samples - got);
|
||||
atomic_fetch_add(&p->buffer_starvation, 1);
|
||||
atomic_fetch_add(&g_buffer_starvation, 1);
|
||||
}
|
||||
if (remainder > 0)
|
||||
memset((char *)audio_data + (size_t)samples * stride, 0, (size_t)remainder);
|
||||
atomic_fetch_add(&p->submitted_frames, samples);
|
||||
atomic_fetch_add(&g_submitted_frames, samples);
|
||||
atomic_fetch_sub(&p->callbacks_inflight, 1);
|
||||
return AUDIO_DATA_CALLBACK_RESULT_VALID;
|
||||
}
|
||||
|
||||
static void renderer_error(OH_AudioRenderer *renderer, void *user_data,
|
||||
OH_AudioStream_Result error)
|
||||
{
|
||||
(void)renderer;
|
||||
struct ao *ao = user_data;
|
||||
log_result(ao, "renderer error callback", error);
|
||||
set_state(MPV_HARMONY_AUDIO_ERROR);
|
||||
ao_request_reload(ao);
|
||||
}
|
||||
|
||||
static void output_device_changed(OH_AudioRenderer *renderer, void *user_data,
|
||||
OH_AudioStream_DeviceChangeReason reason)
|
||||
{
|
||||
(void)renderer;
|
||||
struct ao *ao = user_data;
|
||||
MP_VERBOSE(ao, "output device changed: %d\n", reason);
|
||||
ao_request_reload(ao);
|
||||
}
|
||||
|
||||
static MP_THREAD_VOID clock_loop(void *arg)
|
||||
{
|
||||
struct ao *ao = arg;
|
||||
struct priv *p = ao->priv;
|
||||
mp_thread_set_name("ao/harmony-clock");
|
||||
while (!atomic_load(&p->stop_clock)) {
|
||||
int64_t played = 0;
|
||||
int64_t timestamp = 0;
|
||||
if (OH_AudioRenderer_GetAudioTimestampInfo(p->renderer, &played, ×tamp) == AUDIOSTREAM_SUCCESS) {
|
||||
atomic_store(&p->raw_played_frames, played);
|
||||
int64_t origin = atomic_load(&p->timestamp_origin_raw);
|
||||
int64_t effective_played = played > origin ? played - origin : 0;
|
||||
atomic_store(&p->played_frames, effective_played);
|
||||
atomic_store(&p->audio_timestamp_ns, timestamp);
|
||||
atomic_store(&g_played_frames, effective_played);
|
||||
atomic_store(&g_audio_timestamp_ns, timestamp);
|
||||
int64_t submitted = atomic_load(&p->submitted_frames);
|
||||
int64_t pending = submitted > effective_played ? submitted - effective_played : 0;
|
||||
int64_t delay = p->latency_ms + pending * 1000 / MPMAX(1, ao->samplerate);
|
||||
atomic_store(&p->estimated_delay_ms, delay);
|
||||
atomic_store(&g_estimated_delay_ms, delay);
|
||||
}
|
||||
mp_sleep_ns(MP_TIME_MS_TO_NS(20));
|
||||
}
|
||||
MP_THREAD_RETURN();
|
||||
}
|
||||
|
||||
static OH_AudioStream_Result create_renderer(struct ao *ao, int format)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
p->renderer = NULL;
|
||||
OH_AudioStreamBuilder *builder = NULL;
|
||||
OH_AudioStream_Result result = OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_RENDERER);
|
||||
if (result != AUDIOSTREAM_SUCCESS || builder == NULL) {
|
||||
log_result(ao, "AUDIO_BUILDER_FAILED", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
OH_AudioStream_SampleFormat sample_format = sample_format_for(format);
|
||||
OH_AudioChannelLayout layout = p->channel_count == 1 ? CH_LAYOUT_MONO : CH_LAYOUT_STEREO;
|
||||
result = OH_AudioStreamBuilder_SetSamplingRate(builder, ao->samplerate);
|
||||
if (result == AUDIOSTREAM_SUCCESS)
|
||||
result = OH_AudioStreamBuilder_SetChannelCount(builder, p->channel_count);
|
||||
if (result == AUDIOSTREAM_SUCCESS)
|
||||
result = OH_AudioStreamBuilder_SetChannelLayout(builder, layout);
|
||||
if (result == AUDIOSTREAM_SUCCESS)
|
||||
result = OH_AudioStreamBuilder_SetSampleFormat(builder, sample_format);
|
||||
if (result == AUDIOSTREAM_SUCCESS)
|
||||
result = OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_RAW);
|
||||
if (result == AUDIOSTREAM_SUCCESS)
|
||||
result = OH_AudioStreamBuilder_SetLatencyMode(builder, AUDIOSTREAM_LATENCY_MODE_NORMAL);
|
||||
if (result == AUDIOSTREAM_SUCCESS)
|
||||
result = OH_AudioStreamBuilder_SetRendererInfo(builder, AUDIOSTREAM_USAGE_MOVIE);
|
||||
if (result == AUDIOSTREAM_SUCCESS)
|
||||
result = OH_AudioStreamBuilder_SetRendererWriteDataCallback(builder, write_data, ao);
|
||||
if (result == AUDIOSTREAM_SUCCESS)
|
||||
result = OH_AudioStreamBuilder_SetRendererOutputDeviceChangeCallback(builder,
|
||||
output_device_changed, ao);
|
||||
if (result == AUDIOSTREAM_SUCCESS)
|
||||
result = OH_AudioStreamBuilder_SetRendererErrorCallback(builder, renderer_error, ao);
|
||||
if (result == AUDIOSTREAM_SUCCESS)
|
||||
result = OH_AudioStreamBuilder_GenerateRenderer(builder, &p->renderer);
|
||||
OH_AudioStreamBuilder_Destroy(builder);
|
||||
if (result != AUDIOSTREAM_SUCCESS || p->renderer == NULL) {
|
||||
if (p->renderer != NULL) {
|
||||
OH_AudioRenderer_Release(p->renderer);
|
||||
p->renderer = NULL;
|
||||
}
|
||||
log_result(ao, "AUDIO_RENDERER_CREATE_FAILED", result);
|
||||
return result;
|
||||
}
|
||||
if (OH_AudioRenderer_SetVolume(p->renderer, 1.0f) != AUDIOSTREAM_SUCCESS)
|
||||
MP_WARN(ao, "renderer unity volume could not be set\n");
|
||||
if (OH_AudioRenderer_GetFrameSizeInCallback(p->renderer, &p->frame_size) != AUDIOSTREAM_SUCCESS)
|
||||
p->frame_size = 0;
|
||||
if (OH_AudioRenderer_GetLatency(p->renderer, AUDIOSTREAM_LATENCY_TYPE_ALL, &p->latency_ms) != AUDIOSTREAM_SUCCESS)
|
||||
p->latency_ms = 0;
|
||||
MP_VERBOSE(ao, "OHAudio renderer ready: %d Hz, %d channels, %s, frame_size=%d, latency=%d ms\n",
|
||||
ao->samplerate, p->channel_count, af_fmt_to_str(format), p->frame_size, p->latency_ms);
|
||||
return AUDIOSTREAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
reset_stats();
|
||||
atomic_init(&p->stop_clock, false);
|
||||
atomic_init(&p->callback_enabled, false);
|
||||
atomic_init(&p->callbacks_inflight, 0);
|
||||
atomic_init(&p->submitted_frames, 0);
|
||||
atomic_init(&p->played_frames, 0);
|
||||
atomic_init(&p->raw_played_frames, 0);
|
||||
atomic_init(&p->timestamp_origin_raw, 0);
|
||||
atomic_init(&p->audio_timestamp_ns, 0);
|
||||
atomic_init(&p->estimated_delay_ms, 0);
|
||||
atomic_init(&p->buffer_starvation, 0);
|
||||
atomic_init(&p->callback_count, 0);
|
||||
p->renderer = NULL;
|
||||
p->clock_thread_valid = false;
|
||||
p->frame_size = 0;
|
||||
p->latency_ms = 0;
|
||||
|
||||
if (!af_fmt_is_pcm(ao->format)) {
|
||||
MP_ERR(ao, "AUDIO_UNSUPPORTED_FORMAT: %s\n", af_fmt_to_str(ao->format));
|
||||
set_state(MPV_HARMONY_AUDIO_ERROR);
|
||||
return -1;
|
||||
}
|
||||
ao->format = af_fmt_is_float(ao->format) ? AF_FORMAT_FLOAT : AF_FORMAT_S16;
|
||||
if (ao->channels.num < 1) {
|
||||
MP_ERR(ao, "AUDIO_UNSUPPORTED_CHANNEL_LAYOUT\n");
|
||||
set_state(MPV_HARMONY_AUDIO_ERROR);
|
||||
return -1;
|
||||
}
|
||||
p->channel_count = ao->channels.num == 1 ? 1 : 2;
|
||||
if (ao->channels.num != p->channel_count)
|
||||
mp_chmap_from_channels(&ao->channels, p->channel_count);
|
||||
p->channel_layout = p->channel_count == 1 ? CH_LAYOUT_MONO : CH_LAYOUT_STEREO;
|
||||
|
||||
OH_AudioStream_Result result = create_renderer(ao, ao->format);
|
||||
if (result != AUDIOSTREAM_SUCCESS && ao->format == AF_FORMAT_FLOAT) {
|
||||
ao->format = AF_FORMAT_S16;
|
||||
result = create_renderer(ao, ao->format);
|
||||
}
|
||||
if (result != AUDIOSTREAM_SUCCESS) {
|
||||
set_state(MPV_HARMONY_AUDIO_ERROR);
|
||||
return -1;
|
||||
}
|
||||
atomic_store(&g_sample_rate, ao->samplerate);
|
||||
atomic_store(&g_sample_format, ao->format);
|
||||
atomic_store(&g_channels, p->channel_count);
|
||||
atomic_store(&g_channel_layout, p->channel_layout);
|
||||
atomic_store(&g_estimated_delay_ms, p->latency_ms);
|
||||
set_state(MPV_HARMONY_AUDIO_READY);
|
||||
if (mp_thread_create(&p->clock_thread, clock_loop, ao)) {
|
||||
MP_ERR(ao, "AUDIO_TIMESTAMP_ERROR: clock thread creation failed\n");
|
||||
OH_AudioRenderer_Release(p->renderer);
|
||||
p->renderer = NULL;
|
||||
set_state(MPV_HARMONY_AUDIO_ERROR);
|
||||
return -1;
|
||||
}
|
||||
p->clock_thread_valid = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
MP_VERBOSE(ao, "OHAudio release\n");
|
||||
atomic_store(&p->callback_enabled, false);
|
||||
if (p->renderer != NULL) {
|
||||
OH_AudioRenderer_Stop(p->renderer);
|
||||
while (atomic_load(&p->callbacks_inflight) > 0)
|
||||
mp_sleep_ns(MP_TIME_MS_TO_NS(1));
|
||||
}
|
||||
atomic_store(&p->stop_clock, true);
|
||||
if (p->clock_thread_valid)
|
||||
mp_thread_join(p->clock_thread);
|
||||
if (p->renderer != NULL) {
|
||||
OH_AudioStream_Result result = OH_AudioRenderer_Release(p->renderer);
|
||||
if (result != AUDIOSTREAM_SUCCESS)
|
||||
log_result(ao, "AUDIO_RELEASE_ERROR", result);
|
||||
p->renderer = NULL;
|
||||
}
|
||||
set_state(MPV_HARMONY_AUDIO_RELEASED);
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (p->renderer == NULL)
|
||||
return;
|
||||
atomic_store(&p->callback_enabled, true);
|
||||
OH_AudioStream_Result result = OH_AudioRenderer_Start(p->renderer);
|
||||
if (result != AUDIOSTREAM_SUCCESS) {
|
||||
log_result(ao, "AUDIO_RENDERER_START_FAILED", result);
|
||||
set_state(MPV_HARMONY_AUDIO_ERROR);
|
||||
ao_request_reload(ao);
|
||||
return;
|
||||
}
|
||||
MP_VERBOSE(ao, "OHAudio start\n");
|
||||
set_state(MPV_HARMONY_AUDIO_RUNNING);
|
||||
}
|
||||
|
||||
static bool set_pause(struct ao *ao, bool paused)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (p->renderer == NULL)
|
||||
return false;
|
||||
OH_AudioStream_Result result = paused ? OH_AudioRenderer_Pause(p->renderer)
|
||||
: OH_AudioRenderer_Start(p->renderer);
|
||||
if (result != AUDIOSTREAM_SUCCESS) {
|
||||
log_result(ao, paused ? "pause" : "resume", result);
|
||||
set_state(MPV_HARMONY_AUDIO_ERROR);
|
||||
return false;
|
||||
}
|
||||
MP_VERBOSE(ao, "OHAudio %s\n", paused ? "pause" : "resume");
|
||||
set_state(paused ? MPV_HARMONY_AUDIO_PAUSED : MPV_HARMONY_AUDIO_RUNNING);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (p->renderer == NULL)
|
||||
return;
|
||||
atomic_store(&p->callback_enabled, false);
|
||||
OH_AudioStream_Result stop_result = OH_AudioRenderer_Stop(p->renderer);
|
||||
if (stop_result != AUDIOSTREAM_SUCCESS && stop_result != AUDIOSTREAM_ERROR_ILLEGAL_STATE)
|
||||
log_result(ao, "AUDIO_STOP_FAILED", stop_result);
|
||||
while (atomic_load(&p->callbacks_inflight) > 0)
|
||||
mp_sleep_ns(MP_TIME_MS_TO_NS(1));
|
||||
OH_AudioStream_Result result = OH_AudioRenderer_Flush(p->renderer);
|
||||
if (result != AUDIOSTREAM_SUCCESS) {
|
||||
log_result(ao, "AUDIO_FLUSH_FAILED", result);
|
||||
set_state(MPV_HARMONY_AUDIO_ERROR);
|
||||
return;
|
||||
}
|
||||
atomic_store(&p->timestamp_origin_raw, atomic_load(&p->raw_played_frames));
|
||||
atomic_store(&p->submitted_frames, 0);
|
||||
atomic_store(&p->played_frames, 0);
|
||||
atomic_store(&p->audio_timestamp_ns, 0);
|
||||
atomic_store(&g_submitted_frames, 0);
|
||||
atomic_store(&g_played_frames, 0);
|
||||
atomic_store(&g_audio_timestamp_ns, 0);
|
||||
atomic_fetch_add(&g_audio_flush_count, 1);
|
||||
atomic_fetch_add(&g_seek_audio_reset_count, 1);
|
||||
MP_VERBOSE(ao, "OHAudio flush\n");
|
||||
set_state(MPV_HARMONY_AUDIO_STOPPED);
|
||||
}
|
||||
|
||||
static void get_state(struct ao *ao, struct mp_pcm_state *state)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int64_t submitted = atomic_load(&p->submitted_frames);
|
||||
int64_t played = atomic_load(&p->played_frames);
|
||||
int64_t pending = submitted > played ? submitted - played : 0;
|
||||
state->free_samples = 0;
|
||||
state->queued_samples = pending;
|
||||
state->delay = pending / (double)MPMAX(1, ao->samplerate) + p->latency_ms / 1000.0;
|
||||
state->playing = atomic_load(&g_state) == MPV_HARMONY_AUDIO_RUNNING;
|
||||
}
|
||||
|
||||
const struct ao_driver audio_out_harmony = {
|
||||
.description = "HarmonyOS OHAudio output",
|
||||
.name = "harmony",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.reset = reset,
|
||||
.set_pause = set_pause,
|
||||
.start = start,
|
||||
.get_state = get_state,
|
||||
.priv_size = sizeof(struct priv),
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
MPV_EXPORT int mpv_harmony_audio_get_stats(struct mpv_harmony_audio_stats *stats)
|
||||
{
|
||||
if (stats != NULL)
|
||||
memset(stats, 0, sizeof(*stats));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if HAVE_HARMONY_AUDIO
|
||||
MPV_EXPORT int mpv_harmony_audio_get_stats(struct mpv_harmony_audio_stats *stats)
|
||||
{
|
||||
if (stats == NULL)
|
||||
return 0;
|
||||
stats->state = atomic_load(&g_state);
|
||||
stats->sample_rate = atomic_load(&g_sample_rate);
|
||||
stats->sample_format = atomic_load(&g_sample_format);
|
||||
stats->channels = atomic_load(&g_channels);
|
||||
stats->channel_layout = atomic_load(&g_channel_layout);
|
||||
stats->submitted_frames = atomic_load(&g_submitted_frames);
|
||||
stats->played_frames = atomic_load(&g_played_frames);
|
||||
stats->audio_timestamp_ns = atomic_load(&g_audio_timestamp_ns);
|
||||
stats->estimated_delay_ms = atomic_load(&g_estimated_delay_ms);
|
||||
stats->buffer_starvation = atomic_load(&g_buffer_starvation);
|
||||
stats->system_underrun = atomic_load(&g_system_underrun);
|
||||
stats->callback_count = atomic_load(&g_callback_count);
|
||||
stats->audio_flush_count = atomic_load(&g_audio_flush_count);
|
||||
stats->seek_audio_reset_count = atomic_load(&g_seek_audio_reset_count);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* HarmonyOS audio diagnostics exported for the isolated Playback Lab.
|
||||
* The AO remains usable without consuming this optional diagnostics API.
|
||||
*/
|
||||
#ifndef MPV_AO_HARMONY_H
|
||||
#define MPV_AO_HARMONY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mpv/client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum mpv_harmony_audio_state {
|
||||
MPV_HARMONY_AUDIO_DISABLED = 0,
|
||||
MPV_HARMONY_AUDIO_READY = 1,
|
||||
MPV_HARMONY_AUDIO_RUNNING = 2,
|
||||
MPV_HARMONY_AUDIO_PAUSED = 3,
|
||||
MPV_HARMONY_AUDIO_STOPPED = 4,
|
||||
MPV_HARMONY_AUDIO_ERROR = 5,
|
||||
MPV_HARMONY_AUDIO_RELEASED = 6,
|
||||
};
|
||||
|
||||
struct mpv_harmony_audio_stats {
|
||||
int32_t state;
|
||||
int32_t sample_rate;
|
||||
int32_t sample_format;
|
||||
int32_t channels;
|
||||
uint64_t channel_layout;
|
||||
int64_t submitted_frames;
|
||||
int64_t played_frames;
|
||||
int64_t audio_timestamp_ns;
|
||||
int64_t estimated_delay_ms;
|
||||
int64_t buffer_starvation;
|
||||
int64_t system_underrun;
|
||||
int64_t callback_count;
|
||||
int64_t audio_flush_count;
|
||||
int64_t seek_audio_reset_count;
|
||||
};
|
||||
|
||||
MPV_EXPORT int mpv_harmony_audio_get_stats(struct mpv_harmony_audio_stats *stats);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user