harmony: audio: add OHAudio output backend

This commit is contained in:
BoatingCloud committed 2026-08-29 10:07:13 +08:00
1 parent c650a4214d
commit 7021f883cb
2 files changed
+495

No files matched your search

+446
View File
@@ -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, &timestamp) == 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
+49
View File
@@ -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