From 3f6132637b5538ab2847eabe5258c89b9f0f9b7b Mon Sep 17 00:00:00 2001 From: BoatingCloud <1759888028@qq.com> Date: Sat, 29 Aug 2026 12:36:14 +0800 Subject: [PATCH] feat: add Harmony OHCodec hwdec adapter --- meson.build | 50 +++++++ meson.options | 2 + scripts/build-all-ohos.sh | 1 + scripts/build-mpv-ohos-software.sh | 39 +++++ scripts/build-mpv-ohos.sh | 3 +- video/fmt-conversion.c | 3 + video/harmony_hwdec.c | 118 +++++++++++++++ video/harmony_hwdec.h | 76 ++++++++++ video/hwdec.c | 3 + video/hwdec.h | 1 + video/img_format.h | 1 + video/out/gpu/hwdec.c | 4 + video/out/hwdec/hwdec_ohcodec.c | 223 +++++++++++++++++++++++++++++ video/out/hwdec/hwdec_ohcodec_gl.c | 56 ++++++++ 14 files changed, 579 insertions(+), 1 deletion(-) create mode 100755 scripts/build-mpv-ohos-software.sh create mode 100644 video/harmony_hwdec.c create mode 100644 video/harmony_hwdec.h create mode 100644 video/out/hwdec/hwdec_ohcodec.c create mode 100644 video/out/hwdec/hwdec_ohcodec_gl.c diff --git a/meson.build b/meson.build index dd3ea60dcb..922a623646 100644 --- a/meson.build +++ b/meson.build @@ -225,6 +225,7 @@ sources = files( 'video/filter/vf_format.c', 'video/filter/vf_sub.c', 'video/fmt-conversion.c', + 'video/harmony_hwdec.c', 'video/hwdec.c', 'video/image_loader.c', 'video/image_writer.c', @@ -931,6 +932,35 @@ if features['harmony-audio'] dependencies += harmony_audio endif +harmony_ohcodec_opt = get_option('harmony-ohcodec').require( + ohos, + error_message: 'HarmonyOS OHCodec is only available on OHOS targets!', +) +harmony_ohcodec_libs = [ + cc.find_library('native_media_codecbase', required: harmony_ohcodec_opt), + cc.find_library('native_media_core', required: harmony_ohcodec_opt), + cc.find_library('native_media_vdec', required: harmony_ohcodec_opt), + cc.find_library('native_window', required: harmony_ohcodec_opt), +] +harmony_ohcodec_headers = cc.has_header('multimedia/player_framework/native_avcapability.h', + required: harmony_ohcodec_opt) and \ + cc.has_header('multimedia/player_framework/native_avcodec_videodecoder.h', + required: harmony_ohcodec_opt) and \ + cc.has_header('multimedia/player_framework/native_avbuffer.h', + required: harmony_ohcodec_opt) and \ + cc.has_header('native_window/external_window.h', + required: harmony_ohcodec_opt) +features += {'harmony-ohcodec': harmony_ohcodec_headers and harmony_ohcodec_libs[0].found() and + harmony_ohcodec_libs[1].found() and harmony_ohcodec_libs[2].found() and + harmony_ohcodec_libs[3].found()} +if harmony_ohcodec_opt.enabled() and not features['harmony-ohcodec'] + error('HarmonyOS OHCodec headers or native media libraries were not found') +endif +if features['harmony-ohcodec'] + dependencies += harmony_ohcodec_libs + sources += files('video/out/hwdec/hwdec_ohcodec.c') +endif + opensles = cc.find_library('OpenSLES', required: get_option('opensles')) features += {'opensles': opensles.found()} if features['opensles'] @@ -1333,6 +1363,26 @@ if plain_gl.allowed() features += {'gl': true} endif +harmony_ohcodec_gl_opt = get_option('harmony-ohcodec-gl').require( + features['harmony-ohcodec'] and features['gl'] and ohos, + error_message: 'HarmonyOS OHCodec GPU interop requires OHCodec and OpenGL!', +) +harmony_ohcodec_gl_egl = cc.find_library('EGL', required: harmony_ohcodec_gl_opt) +harmony_ohcodec_gl_lib = cc.find_library('native_buffer', required: harmony_ohcodec_gl_opt) +harmony_ohcodec_gl_headers = cc.has_header('native_buffer/native_buffer.h', + required: harmony_ohcodec_gl_opt) and \ + cc.has_header('EGL/eglext.h', required: harmony_ohcodec_gl_opt) and \ + cc.has_header('GLES2/gl2ext.h', required: harmony_ohcodec_gl_opt) +features += {'harmony-ohcodec-gl': harmony_ohcodec_gl_headers and + harmony_ohcodec_gl_egl.found() and harmony_ohcodec_gl_lib.found()} +if harmony_ohcodec_gl_opt.enabled() and not features['harmony-ohcodec-gl'] + error('HarmonyOS OHCodec GPU interop headers or EGL/native_buffer libraries were not found') +endif +if features['harmony-ohcodec-gl'] + dependencies += [harmony_ohcodec_gl_egl, harmony_ohcodec_gl_lib] + sources += files('video/out/hwdec/hwdec_ohcodec_gl.c') +endif + if features['egl'] or features['egl-android'] or features['egl-angle-win32'] sources += files('video/out/opengl/egl_helpers.c') endif diff --git a/meson.options b/meson.options index cb21436e44..78d65982e3 100644 --- a/meson.options +++ b/meson.options @@ -51,6 +51,8 @@ option('openal', type: 'feature', value: 'disabled', description: 'OpenAL audio option('audiotrack', type: 'feature', value: 'auto', description: 'Android AudioTrack audio output') option('aaudio', type: 'feature', value: 'auto', description: 'Android AAudio audio output') option('harmony-audio', type: 'feature', value: 'auto', description: 'HarmonyOS OHAudio output') +option('harmony-ohcodec', type: 'feature', value: 'auto', description: 'HarmonyOS OHCodec hardware decode') +option('harmony-ohcodec-gl', type: 'feature', value: 'disabled', description: 'EXPERIMENTAL HarmonyOS OHCodec GPU interop') option('opensles', type: 'feature', value: 'auto', description: 'OpenSL ES audio output') option('oss-audio', type: 'feature', value: 'auto', description: 'OSSv4 audio output') option('pipewire', type: 'feature', value: 'auto', description: 'PipeWire audio output') diff --git a/scripts/build-all-ohos.sh b/scripts/build-all-ohos.sh index aa67c1a97e..a851d96415 100755 --- a/scripts/build-all-ohos.sh +++ b/scripts/build-all-ohos.sh @@ -13,4 +13,5 @@ PROJECT_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) "$PROJECT_ROOT/scripts/build-font-stack-ohos.sh" "$PROJECT_ROOT/scripts/build-libplacebo-ohos.sh" "$PROJECT_ROOT/scripts/build-mpv-ohos.sh" +"$PROJECT_ROOT/scripts/build-mpv-ohos-software.sh" "$PROJECT_ROOT/scripts/verify-ohos-prefix.sh" diff --git a/scripts/build-mpv-ohos-software.sh b/scripts/build-mpv-ohos-software.sh new file mode 100755 index 0000000000..5822c900d5 --- /dev/null +++ b/scripts/build-mpv-ohos-software.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +set -eu + +. "$(dirname -- "$0")/setup-ohos-env.sh" +BUILD=$OHOS_BUILD_ROOT/build/mpv-software +LOG=$OHOS_BUILD_ROOT/logs/mpv-software.log + +mkdir -p "$OHOS_BUILD_ROOT/logs" +test -f "$PREFIX/lib/libavcodec.a" || "$PROJECT_ROOT/scripts/build-ffmpeg-ohos.sh" +test -f "$PREFIX/lib/libass.a" || "$PROJECT_ROOT/scripts/build-font-stack-ohos.sh" +test -f "$PREFIX/lib/libplacebo.a" || "$PROJECT_ROOT/scripts/build-libplacebo-ohos.sh" + +if test -f "$BUILD/meson-private/coredata.dat"; then + setup_mode=--wipe +else + setup_mode= +fi + +if ! meson setup "$BUILD" "$PROJECT_ROOT" $setup_mode \ + --cross-file "$CROSS_FILE" --prefix "$PREFIX" --buildtype release \ + -Ddefault_library=shared -Dauto_features=disabled \ + -Dcplayer=false -Dlibmpv=true -Dtests=false -Dfuzzers=false \ + -Dgpl=true -Dbuild-date=false -Dplain-gl=enabled -Dgl=enabled \ + -Dzlib=enabled -Dvector=enabled -Dlua=disabled -Dharmony-audio=enabled \ + -Dharmony-ohcodec=disabled -Dharmony-ohcodec-gl=disabled > "$LOG" 2>&1; then + tail -n 180 "$LOG" >&2 + exit 1 +fi + +if ! meson compile -C "$BUILD" >> "$LOG" 2>&1; then + tail -n 220 "$LOG" >&2 + exit 1 +fi + +LIBMPV=$BUILD/libmpv.so +test -f "$LIBMPV" || { printf 'Missing software libmpv: %s\n' "$LIBMPV" >&2; exit 1; } +"$PROJECT_ROOT/scripts/verify-ohos-binary.sh" "$LIBMPV" +printf 'software-libmpv=%s\n' "$LIBMPV" diff --git a/scripts/build-mpv-ohos.sh b/scripts/build-mpv-ohos.sh index 51535d052e..d85e34b58a 100755 --- a/scripts/build-mpv-ohos.sh +++ b/scripts/build-mpv-ohos.sh @@ -22,7 +22,8 @@ if ! meson setup "$BUILD" "$PROJECT_ROOT" $setup_mode \ -Ddefault_library=shared -Dauto_features=disabled \ -Dcplayer=false -Dlibmpv=true -Dtests=false -Dfuzzers=false \ -Dgpl=true -Dbuild-date=false -Dplain-gl=enabled -Dgl=enabled \ - -Dzlib=enabled -Dvector=enabled -Dlua=disabled -Dharmony-audio=enabled > "$LOG" 2>&1; then + -Dzlib=enabled -Dvector=enabled -Dlua=disabled -Dharmony-audio=enabled \ + -Dharmony-ohcodec=enabled -Dharmony-ohcodec-gl=disabled > "$LOG" 2>&1; then tail -n 180 "$LOG" >&2 exit 1 fi diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c index 9804d6b9f1..dfd42de28e 100644 --- a/video/fmt-conversion.c +++ b/video/fmt-conversion.c @@ -60,6 +60,9 @@ static const struct { {IMGFMT_VDPAU, AV_PIX_FMT_VDPAU}, {IMGFMT_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX}, {IMGFMT_MEDIACODEC, AV_PIX_FMT_MEDIACODEC}, +#if HAVE_HARMONY_OHCODEC + {IMGFMT_OHCODEC, AV_PIX_FMT_OHCODEC}, +#endif {IMGFMT_VAAPI, AV_PIX_FMT_VAAPI}, {IMGFMT_DXVA2, AV_PIX_FMT_DXVA2_VLD}, {IMGFMT_D3D11, AV_PIX_FMT_D3D11}, diff --git a/video/harmony_hwdec.c b/video/harmony_hwdec.c new file mode 100644 index 0000000000..fa36d7ef16 --- /dev/null +++ b/video/harmony_hwdec.c @@ -0,0 +1,118 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#include +#include +#include + +#include "video/harmony_hwdec.h" + +static pthread_mutex_t stats_lock = PTHREAD_MUTEX_INITIALIZER; +static struct mpv_harmony_hwdec_stats stats = { + .state = MP_HARMONY_HWDEC_DISABLED, +}; + +static void copy_text(char *dst, const char *src) +{ + snprintf(dst, MP_HARMONY_HWDEC_TEXT, "%s", src ? src : ""); +} + +MPV_EXPORT void mpv_harmony_hwdec_set_requested(int requested) +{ + pthread_mutex_lock(&stats_lock); + stats.hwdec_requested = !!requested; + stats.state = requested ? MP_HARMONY_HWDEC_REQUESTED : MP_HARMONY_HWDEC_DISABLED; + if (!requested) { + stats.hwdec_active = 0; + stats.fallback_reason[0] = '\0'; + stats.decoder_name[0] = '\0'; + } + pthread_mutex_unlock(&stats_lock); +} + +MPV_EXPORT void mpv_harmony_hwdec_set_state(enum mp_harmony_hwdec_state state) +{ + pthread_mutex_lock(&stats_lock); + stats.state = state; + pthread_mutex_unlock(&stats_lock); +} + +MPV_EXPORT void mpv_harmony_hwdec_set_active(const char *backend, + const char *device_type) +{ + pthread_mutex_lock(&stats_lock); + stats.hwdec_active = 1; + stats.state = MP_HARMONY_HWDEC_ACTIVE; + copy_text(stats.hwdec_backend, backend); + copy_text(stats.hw_device_type, device_type); + pthread_mutex_unlock(&stats_lock); +} + +MPV_EXPORT void mpv_harmony_hwdec_set_decoder(const char *decoder_name) +{ + pthread_mutex_lock(&stats_lock); + copy_text(stats.decoder_name, decoder_name); + pthread_mutex_unlock(&stats_lock); +} + +MPV_EXPORT void mpv_harmony_hwdec_set_capability(const char *summary) +{ + pthread_mutex_lock(&stats_lock); + copy_text(stats.capability_summary, summary); + pthread_mutex_unlock(&stats_lock); +} + +MPV_EXPORT void mpv_harmony_hwdec_set_fallback(const char *reason) +{ + pthread_mutex_lock(&stats_lock); + stats.hwdec_active = 0; + stats.state = MP_HARMONY_HWDEC_FALLBACK; + stats.hw_fallback_count++; + copy_text(stats.fallback_reason, reason); + pthread_mutex_unlock(&stats_lock); +} + +MPV_EXPORT void mpv_harmony_hwdec_record_hardware_frame(void) +{ + pthread_mutex_lock(&stats_lock); + stats.hardware_frame_count++; + pthread_mutex_unlock(&stats_lock); +} + +MPV_EXPORT void mpv_harmony_hwdec_record_software_frame(void) +{ + pthread_mutex_lock(&stats_lock); + stats.software_frame_count++; + pthread_mutex_unlock(&stats_lock); +} + +MPV_EXPORT void mpv_harmony_hwdec_record_surface_present(void) +{ + pthread_mutex_lock(&stats_lock); + stats.surface_present_count++; + pthread_mutex_unlock(&stats_lock); +} + +MPV_EXPORT void mpv_harmony_hwdec_record_gpu_interop_frame(void) +{ + pthread_mutex_lock(&stats_lock); + stats.gpu_interop_frame_count++; + stats.native_buffer_import_count++; + pthread_mutex_unlock(&stats_lock); +} + +MPV_EXPORT int mpv_harmony_hwdec_get_stats(struct mpv_harmony_hwdec_stats *out) +{ + if (!out) + return 0; + pthread_mutex_lock(&stats_lock); + *out = stats; + pthread_mutex_unlock(&stats_lock); + return 1; +} diff --git a/video/harmony_hwdec.h b/video/harmony_hwdec.h new file mode 100644 index 0000000000..822eebcd2e --- /dev/null +++ b/video/harmony_hwdec.h @@ -0,0 +1,76 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#ifndef MPV_HARMONY_HWDEC_H +#define MPV_HARMONY_HWDEC_H + +#include + +#include "mpv/client.h" + +struct OH_AVBuffer; +struct mp_log; + +#ifdef __cplusplus +extern "C" { +#endif + +enum mp_harmony_hwdec_state { + MP_HARMONY_HWDEC_DISABLED, + MP_HARMONY_HWDEC_REQUESTED, + MP_HARMONY_HWDEC_CAPABILITY_CHECK, + MP_HARMONY_HWDEC_DEVICE_CREATED, + MP_HARMONY_HWDEC_DECODER_CREATED, + MP_HARMONY_HWDEC_DIRECT_SURFACE, + MP_HARMONY_HWDEC_GPU_INTEROP, + MP_HARMONY_HWDEC_ACTIVE, + MP_HARMONY_HWDEC_FALLBACK, + MP_HARMONY_HWDEC_ERROR, +}; + +#define MP_HARMONY_HWDEC_TEXT 128 + +struct mpv_harmony_hwdec_stats { + int hwdec_requested; + int hwdec_active; + int state; + int64_t hardware_frame_count; + int64_t software_frame_count; + int64_t hw_fallback_count; + int64_t native_buffer_import_count; + int64_t native_buffer_import_failure; + int64_t surface_present_count; + int64_t gpu_interop_frame_count; + char hwdec_backend[MP_HARMONY_HWDEC_TEXT]; + char hw_device_type[MP_HARMONY_HWDEC_TEXT]; + char decoder_name[MP_HARMONY_HWDEC_TEXT]; + char fallback_reason[MP_HARMONY_HWDEC_TEXT]; + char capability_summary[MP_HARMONY_HWDEC_TEXT]; +}; + +MPV_EXPORT void mpv_harmony_hwdec_set_requested(int requested); +MPV_EXPORT void mpv_harmony_hwdec_set_state(enum mp_harmony_hwdec_state state); +MPV_EXPORT void mpv_harmony_hwdec_set_active(const char *backend, + const char *device_type); +MPV_EXPORT void mpv_harmony_hwdec_set_decoder(const char *decoder_name); +MPV_EXPORT void mpv_harmony_hwdec_set_capability(const char *summary); +MPV_EXPORT void mpv_harmony_hwdec_set_fallback(const char *reason); +MPV_EXPORT void mpv_harmony_hwdec_record_hardware_frame(void); +MPV_EXPORT void mpv_harmony_hwdec_record_software_frame(void); +MPV_EXPORT void mpv_harmony_hwdec_record_surface_present(void); +MPV_EXPORT void mpv_harmony_hwdec_record_gpu_interop_frame(void); +MPV_EXPORT int mpv_harmony_hwdec_get_stats(struct mpv_harmony_hwdec_stats *stats); +MPV_EXPORT int mpv_harmony_ohcodec_gl_probe(struct mp_log *log, + struct OH_AVBuffer *buffer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/video/hwdec.c b/video/hwdec.c index deba518e82..f52fa917da 100644 --- a/video/hwdec.c +++ b/video/hwdec.c @@ -130,6 +130,9 @@ static const struct hwcontext_fns *const hwcontext_fns[] = { #endif #if HAVE_VDPAU &hwcontext_fns_vdpau, +#endif +#if HAVE_HARMONY_OHCODEC + &hwcontext_fns_ohcodec, #endif NULL, }; diff --git a/video/hwdec.h b/video/hwdec.h index 6b62eb81af..1c464b596b 100644 --- a/video/hwdec.h +++ b/video/hwdec.h @@ -124,5 +124,6 @@ extern const struct hwcontext_fns hwcontext_fns_drmprime; extern const struct hwcontext_fns hwcontext_fns_dxva2; extern const struct hwcontext_fns hwcontext_fns_vaapi; extern const struct hwcontext_fns hwcontext_fns_vdpau; +extern const struct hwcontext_fns hwcontext_fns_ohcodec; #endif diff --git a/video/img_format.h b/video/img_format.h index 8cc7788d76..4c56bb498e 100644 --- a/video/img_format.h +++ b/video/img_format.h @@ -276,6 +276,7 @@ enum mp_imgfmt { IMGFMT_DXVA2, // IDirect3DSurface9 (NV12/P010/P016) IMGFMT_MMAL, // MMAL_BUFFER_HEADER_T IMGFMT_MEDIACODEC, // AVMediaCodecBuffer + IMGFMT_OHCODEC, // OH_AVBuffer backed by HarmonyOS OHCodec IMGFMT_CUDA, // CUDA Buffer // Not an actual format; base for mpv-specific descriptor table. diff --git a/video/out/gpu/hwdec.c b/video/out/gpu/hwdec.c index be39c507d0..e285636b08 100644 --- a/video/out/gpu/hwdec.c +++ b/video/out/gpu/hwdec.c @@ -38,6 +38,7 @@ extern const struct ra_hwdec_driver ra_hwdec_drmprime; extern const struct ra_hwdec_driver ra_hwdec_drmprime_overlay; extern const struct ra_hwdec_driver ra_hwdec_aimagereader; extern const struct ra_hwdec_driver ra_hwdec_vulkan; +extern const struct ra_hwdec_driver ra_hwdec_ohcodec; const struct ra_hwdec_driver *const ra_hwdec_drivers[] = { #if HAVE_D3D_HWACCEL @@ -79,6 +80,9 @@ const struct ra_hwdec_driver *const ra_hwdec_drivers[] = { #if HAVE_VULKAN &ra_hwdec_vulkan, #endif +#if HAVE_HARMONY_OHCODEC + &ra_hwdec_ohcodec, +#endif NULL }; diff --git a/video/out/hwdec/hwdec_ohcodec.c b/video/out/hwdec/hwdec_ohcodec.c new file mode 100644 index 0000000000..f4963adedd --- /dev/null +++ b/video/out/hwdec/hwdec_ohcodec.c @@ -0,0 +1,223 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#include +#include + +#include +#include +#include + +#include +#include + +#include "config.h" + +#include "common/msg.h" +#include "video/harmony_hwdec.h" +#include "video/out/gpu/hwdec.h" + +static const int supported_formats[] = {IMGFMT_OHCODEC, 0}; + +struct priv { + struct mp_hwdec_ctx hwctx; + OHNativeWindow *window; + bool frame_seen; +}; + +static void capability_summary(char *summary, size_t summary_size, + const char *mime, OH_AVCapability *capability) +{ + if (!capability) { + snprintf(summary, summary_size, "%s:unavailable", mime); + return; + } + + OH_AVRange width = {0}, height = {0}, fps = {0}; + const int32_t *profiles = NULL; + uint32_t profile_count = 0; + const int32_t *levels = NULL; + uint32_t level_count = 0; + const int32_t *pixel_formats = NULL; + uint32_t pixel_format_count = 0; + int width_ok = OH_AVCapability_GetVideoWidthRange(capability, &width) == 0; + int height_ok = OH_AVCapability_GetVideoHeightRange(capability, &height) == 0; + int fps_ok = OH_AVCapability_GetVideoFrameRateRange(capability, &fps) == 0; + int profile_ok = OH_AVCapability_GetSupportedProfiles(capability, &profiles, + &profile_count) == 0; + int level_ok = profile_ok && profile_count > 0 && + OH_AVCapability_GetSupportedLevelsForProfile(capability, profiles[0], + &levels, &level_count) == 0; + int pixel_ok = OH_AVCapability_GetVideoSupportedPixelFormats(capability, + &pixel_formats, + &pixel_format_count) == 0; + const char *name = OH_AVCapability_GetName(capability); + snprintf(summary, summary_size, + "%s:name=%s hw=%d width=%s%d-%d height=%s%d-%d fps=%s%d-%d profiles=%s%u levels=%s%u pixfmt=%s%u", + mime, name ? name : "unknown", OH_AVCapability_IsHardware(capability), + width_ok ? "" : "?", width.minVal, width.maxVal, + height_ok ? "" : "?", height.minVal, height.maxVal, + fps_ok ? "" : "?", fps.minVal, fps.maxVal, + profile_ok ? "" : "?", profile_count, + level_ok ? "" : "?", level_count, + pixel_ok ? "" : "?", pixel_format_count); +} + +static int query_capabilities(struct ra_hwdec *hw) +{ + static const struct { + const char *mime; + const char *name; + } codecs[] = { + {"video/avc", "h264"}, + {"video/hevc", "hevc"}, + }; + bool found = false; + char capability_text[MP_HARMONY_HWDEC_TEXT] = {0}; + + mpv_harmony_hwdec_set_state(MP_HARMONY_HWDEC_CAPABILITY_CHECK); + for (int i = 0; i < 2; i++) { + OH_AVCapability *capability = + OH_AVCodec_GetCapabilityByCategory(codecs[i].mime, false, HARDWARE); + char current[MP_HARMONY_HWDEC_TEXT]; + capability_summary(current, sizeof(current), codecs[i].mime, capability); + if (i > 0 && capability_text[0]) + snprintf(capability_text + strlen(capability_text), + sizeof(capability_text) - strlen(capability_text), ";"); + snprintf(capability_text + strlen(capability_text), + sizeof(capability_text) - strlen(capability_text), "%s", current); + if (capability) { + found = true; + MP_VERBOSE(hw, "%s OHCodec capability: %s\n", codecs[i].name, + OH_AVCapability_GetName(capability)); + } else { + MP_VERBOSE(hw, "%s OHCodec hardware capability unavailable\n", + codecs[i].name); + } + } + if (!found) { + mpv_harmony_hwdec_set_fallback("OHCodec hardware capability unavailable"); + return -1; + } + mpv_harmony_hwdec_set_capability(capability_text); + return 0; +} + +static int init(struct ra_hwdec *hw) +{ + struct priv *p = hw->priv; + if (query_capabilities(hw) < 0) + return -1; + p->window = ra_get_native_resource(hw->ra_ctx->ra, "ohos_direct_surface"); + if (!p->window) { + MP_VERBOSE(hw, "direct surface unavailable; refusing to share the libmpv EGL surface\n"); + mpv_harmony_hwdec_set_fallback("exclusive ohos_direct_surface is unavailable"); + return -1; + } + if (OH_NativeWindow_NativeObjectReference(p->window) != 0) { + MP_ERR(hw, "OHNativeWindow reference failed\n"); + mpv_harmony_hwdec_set_fallback("OHNativeWindow reference failed"); + p->window = NULL; + return -1; + } + + AVBufferRef *device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_OHCODEC); + if (!device_ref) { + mpv_harmony_hwdec_set_fallback("OHCodec hwdevice allocation failed"); + OH_NativeWindow_NativeObjectUnreference(p->window); + p->window = NULL; + return -1; + } + AVHWDeviceContext *device = (AVHWDeviceContext *)device_ref->data; + AVOHCodecDeviceContext *oh = device->hwctx; + oh->native_window = p->window; + mpv_harmony_hwdec_set_state(MP_HARMONY_HWDEC_DEVICE_CREATED); + if (av_hwdevice_ctx_init(device_ref) < 0) { + MP_ERR(hw, "OHCodec hwdevice initialization failed\n"); + mpv_harmony_hwdec_set_fallback("OHCodec hwdevice initialization failed"); + av_buffer_unref(&device_ref); + OH_NativeWindow_NativeObjectUnreference(p->window); + p->window = NULL; + return -1; + } + + p->hwctx = (struct mp_hwdec_ctx) { + .driver_name = hw->driver->name, + .av_device_ref = device_ref, + .supported_formats = supported_formats, + .hw_imgfmt = IMGFMT_OHCODEC, + }; + hwdec_devices_add(hw->devs, &p->hwctx); + mpv_harmony_hwdec_set_state(MP_HARMONY_HWDEC_DIRECT_SURFACE); + MP_WARN(hw, "EXPERIMENTAL direct surface mode owns a separate OHNativeWindow; " + "OpenGL composition is not used\n"); + return 0; +} + +static int overlay_frame(struct ra_hwdec *hw, struct mp_image *image, + struct mp_rect *src, struct mp_rect *dst, bool newframe) +{ + (void)src; + (void)dst; + struct priv *p = hw->priv; + if (image && newframe) { + if (!p->frame_seen) { + p->frame_seen = true; + mpv_harmony_hwdec_set_state(MP_HARMONY_HWDEC_DECODER_CREATED); + mpv_harmony_hwdec_set_active(hw->driver->name, "ohcodec"); + } + mpv_harmony_hwdec_record_hardware_frame(); + } + return 0; +} + +static void uninit(struct ra_hwdec *hw) +{ + struct priv *p = hw->priv; + hwdec_devices_remove(hw->devs, &p->hwctx); + av_buffer_unref(&p->hwctx.av_device_ref); + if (p->window) { + OH_NativeWindow_NativeObjectUnreference(p->window); + p->window = NULL; + } + if (p->hwctx.av_device_ref == NULL) + mpv_harmony_hwdec_set_state(MP_HARMONY_HWDEC_DISABLED); +} + +const struct ra_hwdec_driver ra_hwdec_ohcodec = { + .name = "ohcodec-direct-surface-experimental", + .priv_size = sizeof(struct priv), + .imgfmts = {IMGFMT_OHCODEC, 0}, + .device_type = AV_HWDEVICE_TYPE_OHCODEC, + .init = init, + .uninit = uninit, + .overlay_frame = overlay_frame, +}; + +static AVBufferRef *create_dev(struct mpv_global *global, struct mp_log *log, + struct hwcontext_create_dev_params *params) +{ + (void)global; + (void)params; + mp_msg(log, MSGL_V, "OHCodec copy mode requires an exclusive OHNativeWindow; refusing a null-surface device\n"); + mpv_harmony_hwdec_set_fallback("OHCodec copy mode has no exclusive OHNativeWindow"); + return NULL; +} + +static bool is_codec_allowed(AVBufferRef *device_ref, int av_codec_id) +{ + (void)device_ref; + return av_codec_id == AV_CODEC_ID_H264 || av_codec_id == AV_CODEC_ID_HEVC; +} + +const struct hwcontext_fns hwcontext_fns_ohcodec = { + .av_hwdevice_type = AV_HWDEVICE_TYPE_OHCODEC, + .create_dev = create_dev, + .is_codec_allowed = is_codec_allowed, +}; diff --git a/video/out/hwdec/hwdec_ohcodec_gl.c b/video/out/hwdec/hwdec_ohcodec_gl.c new file mode 100644 index 0000000000..c5b3bcc37e --- /dev/null +++ b/video/out/hwdec/hwdec_ohcodec_gl.c @@ -0,0 +1,56 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#define EGL_EGLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES + +#include +#include +#include +#include + +#include +#include +#include + +#include "common/msg.h" +#include "video/harmony_hwdec.h" + +MPV_EXPORT int mpv_harmony_ohcodec_gl_probe(struct mp_log *log, + struct OH_AVBuffer *buffer) +{ + (void)EGL_OHOS_image_native_buffer; + (void)EGL_NATIVE_BUFFER_OHOS; + (void)eglCreateImageKHR; + (void)glEGLImageTargetTexture2DOES; + if (!buffer) { + mp_msg(log, MSGL_V, "OHCodec GL probe requires an OH_AVBuffer\n"); + return -1; + } + + OH_NativeBuffer *native = OH_AVBuffer_GetNativeBuffer(buffer); + if (!native) { + mpv_harmony_hwdec_set_fallback("OH_AVBuffer_GetNativeBuffer failed"); + return -1; + } + OHNativeWindowBuffer *window_buffer = + OH_NativeWindow_CreateNativeWindowBufferFromNativeBuffer(native); + OH_NativeBuffer_Unreference(native); + if (!window_buffer) { + mpv_harmony_hwdec_set_fallback( + "OH_NativeWindowBuffer conversion failed"); + return -1; + } + OH_NativeWindow_DestroyNativeWindowBuffer(window_buffer); + mp_msg(log, MSGL_V, "OHCodec GL probe reached NativeBuffer conversion; " + "upstream output ownership still blocks EGL import\n"); + mpv_harmony_hwdec_set_fallback( + "OHCODEC-BLOCKER-001: upstream has no non-surface output mode"); + return -1; +}