From: Zhao Zhili <quinkblack-at-foxmail.com@ffmpeg.org> To: ffmpeg-devel@ffmpeg.org Cc: Zhao Zhili <zhilizhao@tencent.com> Subject: [FFmpeg-devel] [PATCH 2/2] avcodec/ohdec: Add h264/hevc OpenHarmony decoders Date: Tue, 1 Jul 2025 20:53:16 +0800 Message-ID: <tencent_C026C427C82A90D33F1C68CB86262FD08E06@qq.com> (raw) In-Reply-To: <20250701125257.14490-1-quinkblack@foxmail.com> From: Zhao Zhili <zhilizhao@tencent.com> --- Changelog | 1 + configure | 4 + libavcodec/Makefile | 3 + libavcodec/allcodecs.c | 2 + libavcodec/ohcodec.c | 71 ++++ libavcodec/ohcodec.h | 33 ++ libavcodec/ohdec.c | 788 +++++++++++++++++++++++++++++++++++++++++ libavcodec/version.h | 2 +- 8 files changed, 903 insertions(+), 1 deletion(-) create mode 100644 libavcodec/ohcodec.c create mode 100644 libavcodec/ohcodec.h create mode 100644 libavcodec/ohdec.c diff --git a/Changelog b/Changelog index 81e2cc813f..760769890c 100644 --- a/Changelog +++ b/Changelog @@ -19,6 +19,7 @@ version <next>: - VVC decoder supports all content of SCC (Screen Content Coding): IBC (Inter Block Copy), Palette Mode and ACT (Adaptive Color Transform - G.728 decoder +- OpenHarmony hardware decoder version 7.1: diff --git a/configure b/configure index 6b5ac96bc3..3a6668e8f8 100755 --- a/configure +++ b/configure @@ -3408,6 +3408,8 @@ h264_mf_encoder_deps="mediafoundation" h264_mmal_decoder_deps="mmal" h264_nvenc_encoder_deps="nvenc" h264_nvenc_encoder_select="atsc_a53" +h264_oh_decoder_deps="ohcodec" +h264_oh_decoder_select="h264_mp4toannexb_bsf" h264_omx_encoder_deps="omx" h264_qsv_decoder_select="h264_mp4toannexb_bsf qsvdec" h264_qsv_encoder_select="atsc_a53 qsvenc" @@ -3430,6 +3432,8 @@ hevc_mediacodec_encoder_select="extract_extradata_bsf hevc_metadata" hevc_mf_encoder_deps="mediafoundation" hevc_nvenc_encoder_deps="nvenc" hevc_nvenc_encoder_select="atsc_a53" +hevc_oh_decoder_deps="ohcodec" +hevc_oh_decoder_select="hevc_mp4toannexb_bsf" hevc_qsv_decoder_select="hevc_mp4toannexb_bsf qsvdec" hevc_qsv_encoder_select="hevcparse qsvenc" hevc_rkmpp_decoder_deps="rkmpp" diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 7f963e864d..7ef5c20058 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -428,6 +428,7 @@ OBJS-$(CONFIG_H264_MEDIACODEC_ENCODER) += mediacodecenc.o OBJS-$(CONFIG_H264_MF_ENCODER) += mfenc.o mf_utils.o OBJS-$(CONFIG_H264_MMAL_DECODER) += mmaldec.o OBJS-$(CONFIG_H264_NVENC_ENCODER) += nvenc_h264.o nvenc.o +OBJS-$(CONFIG_H264_OH_DECODER) += ohcodec.o ohdec.o OBJS-$(CONFIG_H264_OMX_ENCODER) += omx.o OBJS-$(CONFIG_H264_QSV_DECODER) += qsvdec.o OBJS-$(CONFIG_H264_QSV_ENCODER) += qsvenc_h264.o @@ -456,6 +457,7 @@ OBJS-$(CONFIG_HEVC_MEDIACODEC_DECODER) += mediacodecdec.o OBJS-$(CONFIG_HEVC_MEDIACODEC_ENCODER) += mediacodecenc.o OBJS-$(CONFIG_HEVC_MF_ENCODER) += mfenc.o mf_utils.o OBJS-$(CONFIG_HEVC_NVENC_ENCODER) += nvenc_hevc.o nvenc.o +OBJS-$(CONFIG_HEVC_OH_DECODER) += ohcodec.o ohdec.o OBJS-$(CONFIG_HEVC_QSV_DECODER) += qsvdec.o OBJS-$(CONFIG_HEVC_QSV_ENCODER) += qsvenc_hevc.o hevc/ps_enc.o OBJS-$(CONFIG_HEVC_RKMPP_DECODER) += rkmppdec.o @@ -1313,6 +1315,7 @@ SKIPHEADERS-$(CONFIG_MEDIACODEC) += mediacodecdec_common.h mediacodec_surf SKIPHEADERS-$(CONFIG_MEDIAFOUNDATION) += mf_utils.h SKIPHEADERS-$(CONFIG_NVDEC) += nvdec.h SKIPHEADERS-$(CONFIG_NVENC) += nvenc.h +SKIPHEADERS-$(CONFIG_OHCODEC) += ohcodec.h SKIPHEADERS-$(CONFIG_QSV) += qsv.h qsv_internal.h SKIPHEADERS-$(CONFIG_QSVENC) += qsvenc.h SKIPHEADERS-$(CONFIG_VAAPI) += vaapi_decode.h vaapi_hevc.h vaapi_encode.h diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 9087b16895..03c32e7a59 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -859,6 +859,7 @@ extern const FFCodec ff_h264_amf_decoder; extern const FFCodec ff_h264_cuvid_decoder; extern const FFCodec ff_h264_mf_encoder; extern const FFCodec ff_h264_nvenc_encoder; +extern const FFCodec ff_h264_oh_decoder; extern const FFCodec ff_h264_omx_encoder; extern const FFCodec ff_h264_qsv_encoder; extern const FFCodec ff_h264_v4l2m2m_encoder; @@ -873,6 +874,7 @@ extern const FFCodec ff_hevc_mediacodec_decoder; extern const FFCodec ff_hevc_mediacodec_encoder; extern const FFCodec ff_hevc_mf_encoder; extern const FFCodec ff_hevc_nvenc_encoder; +extern const FFCodec ff_hevc_oh_decoder; extern const FFCodec ff_hevc_qsv_encoder; extern const FFCodec ff_hevc_v4l2m2m_encoder; extern const FFCodec ff_hevc_vaapi_encoder; diff --git a/libavcodec/ohcodec.c b/libavcodec/ohcodec.c new file mode 100644 index 0000000000..284ff1b2ec --- /dev/null +++ b/libavcodec/ohcodec.c @@ -0,0 +1,71 @@ +/* + * This file is part of FFmpeg. + * + * Copyright (c) 2025 Zhao Zhili <quinkblack@foxmail.com> + * + * FFmpeg 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. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "ohcodec.h" + +#include "libavutil/error.h" + +int ff_oh_err_to_ff_err(OH_AVErrCode err) +{ + switch (err) { + case AV_ERR_OK: + return 0; + case AV_ERR_NO_MEMORY: + return AVERROR(ENOMEM); + case AV_ERR_OPERATE_NOT_PERMIT: + return AVERROR(EPERM); + case AV_ERR_INVALID_VAL: + return AVERROR(EINVAL); + case AV_ERR_IO: + return AVERROR(EIO); + case AV_ERR_TIMEOUT: + return AVERROR(ETIMEDOUT); + case AV_ERR_UNKNOWN: + return AVERROR_UNKNOWN; + case AV_ERR_SERVICE_DIED: + return AVERROR_EXTERNAL; + case AV_ERR_INVALID_STATE: + return AVERROR(EINVAL); + case AV_ERR_UNSUPPORT: + return AVERROR(ENOTSUP); + default: + return AVERROR_EXTERNAL; + } +} + +static const struct { + OH_AVPixelFormat oh_pix; + enum AVPixelFormat pix; +} oh_pix_map[] = { + {AV_PIXEL_FORMAT_NV12, AV_PIX_FMT_NV12}, + {AV_PIXEL_FORMAT_NV21, AV_PIX_FMT_NV21}, + {AV_PIXEL_FORMAT_YUVI420, AV_PIX_FMT_YUV420P}, + {AV_PIXEL_FORMAT_SURFACE_FORMAT, AV_PIX_FMT_OHCODEC}, +}; + +enum AVPixelFormat ff_oh_pix_to_ff_pix(OH_AVPixelFormat oh_pix) +{ + for (size_t i = 0; i < FF_ARRAY_ELEMS(oh_pix_map); i++) + if (oh_pix_map[i].oh_pix == oh_pix) + return oh_pix_map[i].pix; + + return AV_PIX_FMT_NONE; +} + diff --git a/libavcodec/ohcodec.h b/libavcodec/ohcodec.h new file mode 100644 index 0000000000..d51599cd54 --- /dev/null +++ b/libavcodec/ohcodec.h @@ -0,0 +1,33 @@ +/* + * This file is part of FFmpeg. + * + * Copyright (c) 2025 Zhao Zhili <quinkblack@foxmail.com> + * + * FFmpeg 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. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_OHCODEC_H +#define AVCODEC_OHCODEC_H + +#include <multimedia/player_framework/native_averrors.h> +#include <multimedia/player_framework/native_avformat.h> + +#include "libavutil/pixfmt.h" + +int ff_oh_err_to_ff_err(OH_AVErrCode err); + +enum AVPixelFormat ff_oh_pix_to_ff_pix(OH_AVPixelFormat oh_pix); + +#endif diff --git a/libavcodec/ohdec.c b/libavcodec/ohdec.c new file mode 100644 index 0000000000..a52e217ab3 --- /dev/null +++ b/libavcodec/ohdec.c @@ -0,0 +1,788 @@ +/* + * This file is part of FFmpeg. + * + * Copyright (c) 2025 Zhao Zhili <quinkblack@foxmail.com> + * + * FFmpeg 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. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_components.h" + +#include <stdbool.h> +#include <multimedia/player_framework/native_avcapability.h> +#include <multimedia/player_framework/native_avcodec_videodecoder.h> + +#include "libavutil/fifo.h" +#include "libavutil/hwcontext_oh.h" +#include "libavutil/imgutils.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/thread.h" + +#include "avcodec.h" +#include "codec_internal.h" +#include "decode.h" +#include "hwconfig.h" +#include "ohcodec.h" + +typedef struct OHBufferQueueItem { + uint32_t index; + OH_AVBuffer *buffer; +} OHBufferQueueItem; + +typedef struct OHCodecDecContext { + AVClass *avclass; + OH_AVCodec *dec; + /* A reference count to dec. Each hardware frame has a reference count to + * dec. dec will be destroyed only after oh_decode_close and all hardware + * frames have been released. + */ + AVBufferRef *dec_ref; + + AVMutex input_mutex; + AVCond input_cond; + AVFifo *input_queue; + + AVMutex output_mutex; + AVCond output_cond; + AVFifo *output_queue; + + AVPacket pkt; + + int decode_status; + bool eof_sent; + + bool output_to_window; + bool got_stream_info; + int width; + int height; + int stride; + int slice_height; + OH_AVPixelFormat pix_fmt; + + char *name; + int allow_sw; +} OHCodecDecContext; + +typedef struct OHCodecBuffer { + uint32_t index; + OH_AVBuffer *buffer; + AVBufferRef *dec_ref; +} OHCodecBuffer; + +static void oh_decode_release(void *opaque, uint8_t *data) +{ + OH_AVCodec *dec = (OH_AVCodec *)data; + OH_AVErrCode err = OH_VideoDecoder_Destroy(dec); + if (err == AV_ERR_OK) + av_log(NULL, AV_LOG_DEBUG, "Destroy decoder success\n"); + else + av_log(NULL, AV_LOG_ERROR, "Destroy decoder failed, %d, %s\n", + err, av_err2str(ff_oh_err_to_ff_err(err))); +} + +static int oh_decode_create(OHCodecDecContext *s, AVCodecContext *avctx) +{ + const char *name = s->name; + + if (!name) { + const char *mime; + + switch (avctx->codec_id) { + case AV_CODEC_ID_H264: + mime = OH_AVCODEC_MIMETYPE_VIDEO_AVC; + break; + case AV_CODEC_ID_HEVC: + mime = OH_AVCODEC_MIMETYPE_VIDEO_HEVC; + break; + default: + av_log(avctx, AV_LOG_ERROR, "Unknown codec %s\n", + avcodec_get_name(avctx->codec_id)); + return AVERROR_BUG; + } + + OH_AVCapability *cap = OH_AVCodec_GetCapabilityByCategory(mime, false, HARDWARE); + if (!cap) { + if (!s->allow_sw) { + av_log(avctx, AV_LOG_ERROR, "Failed to get hardware codec %s\n", mime); + return AVERROR_EXTERNAL; + } + av_log(avctx, AV_LOG_WARNING, + "Failed to get hardware codec %s, try software backend\n", mime); + cap = OH_AVCodec_GetCapabilityByCategory(mime, false, SOFTWARE); + if (!cap) { + av_log(avctx, AV_LOG_ERROR, "Failed to get software codec %s\n", mime); + return AVERROR_EXTERNAL; + } + } + name = OH_AVCapability_GetName(cap); + if (!name) + return AVERROR_EXTERNAL; + } + + s->dec = OH_VideoDecoder_CreateByName(name); + if (!s->dec) { + av_log(avctx, AV_LOG_ERROR, "Create decoder with name %s failed\n", name); + return AVERROR_EXTERNAL; + } + av_log(avctx, AV_LOG_DEBUG, "Create decoder %s success\n", name); + + s->dec_ref = av_buffer_create((uint8_t *)s->dec, 0, oh_decode_release, + NULL, 0); + if (!s->dec_ref) + return AVERROR(ENOMEM); + + return 0; +} + +static int oh_decode_set_format(OHCodecDecContext *s, AVCodecContext *avctx) +{ + int ret; + OHNativeWindow *window = NULL; + + if (avctx->hw_device_ctx) { + AVHWDeviceContext *device_ctx = (AVHWDeviceContext*)(avctx->hw_device_ctx->data); + if (device_ctx->type == AV_HWDEVICE_TYPE_OHCODEC) { + AVOHCodecDeviceContext *dev = device_ctx->hwctx; + window = dev->native_window; + s->output_to_window = true; + } else { + av_log(avctx, AV_LOG_WARNING, "Ignore invalid hw device type %s\n", + av_hwdevice_get_type_name(device_ctx->type)); + } + } + + if (avctx->width <= 0 || avctx->height <= 0) { + av_log(avctx, AV_LOG_ERROR, + "Invalid width/height (%dx%d), width and height are mandatory for ohcodec\n", + avctx->width, avctx->height); + return AVERROR(EINVAL); + } + + OH_AVFormat *format = OH_AVFormat_Create(); + if (!format) + return AVERROR(ENOMEM); + + OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, avctx->width); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, avctx->height); + if (!s->output_to_window) + OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, + AV_PIXEL_FORMAT_NV12); + else + OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, + AV_PIXEL_FORMAT_SURFACE_FORMAT); + OH_AVErrCode err = OH_VideoDecoder_Configure(s->dec, format); + OH_AVFormat_Destroy(format); + if (err != AV_ERR_OK) { + ret = ff_oh_err_to_ff_err(err); + av_log(avctx, AV_LOG_ERROR, "Decoder configure failed, %d, %s\n", + err, av_err2str(ret)); + return ret; + } + + if (s->output_to_window) { + err = OH_VideoDecoder_SetSurface(s->dec, window); + if (err != AV_ERR_OK) { + ret = ff_oh_err_to_ff_err(err); + av_log(avctx, AV_LOG_ERROR, "Set surface failed, %d, %s\n", + err, av_err2str(ret)); + return ret; + } + } + + return 0; +} + +static void oh_decode_on_err(OH_AVCodec *codec, int32_t err, void *userdata) +{ + AVCodecContext *avctx = userdata; + OHCodecDecContext *s = avctx->priv_data; + + // Careful on the lock order. + // Always lock input first. + ff_mutex_lock(&s->input_mutex); + ff_mutex_lock(&s->output_mutex); + s->decode_status = ff_oh_err_to_ff_err(err); + ff_mutex_unlock(&s->output_mutex); + ff_mutex_unlock(&s->input_mutex); + + ff_cond_signal(&s->output_cond); + ff_cond_signal(&s->input_cond); +} + +static void oh_decode_on_stream_changed(OH_AVCodec *codec, OH_AVFormat *format, + void *userdata) +{ + AVCodecContext *avctx = userdata; + OHCodecDecContext *s = avctx->priv_data; + int32_t n; + double d; + + if (!OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_WIDTH, &s->width) || + !OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_HEIGHT, &s->height) || + !OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_STRIDE, &s->stride) || + !OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_SLICE_HEIGHT, + &s->slice_height)) { + av_log(avctx, AV_LOG_ERROR, "Get dimension info from format failed\n"); + goto out; + } + + if (ff_set_dimensions(avctx, s->width, s->height) < 0) + goto out; + + if (s->stride <= 0 || s->slice_height <= 0) { + av_log(avctx, AV_LOG_ERROR, + "Buffer stride (%d) or slice height (%d) is invalid\n", + s->stride, s->slice_height); + goto out; + } + + if (OH_AVFormat_GetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, &n)) { + s->pix_fmt = n; + /* When use output_to_window, the returned format is the memory + * layout of hardware frame, not AV_PIXEL_FORMAT_SURFACE_FORMAT as + * expected. + */ + if (s->output_to_window) + avctx->pix_fmt = AV_PIX_FMT_OHCODEC; + else + avctx->pix_fmt = ff_oh_pix_to_ff_pix(s->pix_fmt); + // Check whether this pixel format is supported + if (avctx->pix_fmt == AV_PIX_FMT_NONE) { + av_log(avctx, AV_LOG_ERROR, "Unsupported OH_AVPixelFormat %d\n", + n); + goto out; + } + } else { + av_log(avctx, AV_LOG_ERROR, "Failed to get pixel format\n"); + goto out; + } + + if (OH_AVFormat_GetIntValue(format, + OH_MD_KEY_MATRIX_COEFFICIENTS, + &n)) + avctx->colorspace = n; + if (OH_AVFormat_GetIntValue(format, + OH_MD_KEY_COLOR_PRIMARIES, + &n)) + avctx->color_primaries = n; + if (OH_AVFormat_GetIntValue(format, + OH_MD_KEY_TRANSFER_CHARACTERISTICS, + &n)) + avctx->color_trc = n; + if (OH_AVFormat_GetIntValue(format, + OH_MD_KEY_RANGE_FLAG, + &n)) + avctx->color_range = n ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; + + if (OH_AVFormat_GetDoubleValue(format, OH_MD_KEY_VIDEO_SAR, &d)) { + AVRational sar = av_d2q(d, 4096 * 4); + ff_set_sar(avctx, sar); + } + + s->got_stream_info = true; + + return; +out: + av_log(avctx, AV_ERR_OK, "Invalid format from decoder: %s\n", + OH_AVFormat_DumpInfo(format)); + oh_decode_on_err(codec, AV_ERR_UNKNOWN, userdata); +} + +static void oh_decode_on_need_input(OH_AVCodec *codec, uint32_t index, + OH_AVBuffer *buffer, void *userdata) +{ + AVCodecContext *avctx = userdata; + OHCodecDecContext *s = avctx->priv_data; + OHBufferQueueItem item = { + index, buffer, + }; + + ff_mutex_lock(&s->input_mutex); + int ret = av_fifo_write(s->input_queue, &item, 1); + if (ret >= 0) + ff_cond_signal(&s->input_cond); + ff_mutex_unlock(&s->input_mutex); + + if (ret < 0) + oh_decode_on_err(codec, AV_ERR_NO_MEMORY, userdata); +} + +static void oh_decode_on_output(OH_AVCodec *codec, uint32_t index, + OH_AVBuffer *buffer, void *userdata) +{ + AVCodecContext *avctx = userdata; + OHCodecDecContext *s = avctx->priv_data; + OHBufferQueueItem item = { + index, buffer, + }; + + ff_mutex_lock(&s->output_mutex); + int ret = av_fifo_write(s->output_queue, &item, 1); + if (ret >= 0) + ff_cond_signal(&s->output_cond); + ff_mutex_unlock(&s->output_mutex); + + if (ret < 0) + oh_decode_on_err(codec, AV_ERR_NO_MEMORY, userdata); +} + +static int oh_decode_start(OHCodecDecContext *s, AVCodecContext *avctx) +{ + int ret; + OH_AVErrCode err; + OH_AVCodecCallback cb = { + .onError = oh_decode_on_err, + .onStreamChanged = oh_decode_on_stream_changed, + .onNeedInputBuffer = oh_decode_on_need_input, + .onNewOutputBuffer = oh_decode_on_output, + }; + + err = OH_VideoDecoder_RegisterCallback(s->dec, cb, avctx); + if (err != AV_ERR_OK) { + ret = ff_oh_err_to_ff_err(err); + av_log(avctx, AV_LOG_ERROR, "Register callback failed, %d, %s\n", + err, av_err2str(ret)); + return ret; + } + err = OH_VideoDecoder_Prepare(s->dec); + if (err != AV_ERR_OK) { + ret = ff_oh_err_to_ff_err(err); + av_log(avctx, AV_LOG_ERROR, "Prepare failed, %d, %s\n", + err, av_err2str(ret)); + return ret; + } + err = OH_VideoDecoder_Start(s->dec); + if (err != AV_ERR_OK) { + ret = ff_oh_err_to_ff_err(err); + av_log(avctx, AV_LOG_ERROR, "Start failed, %d, %s\n", + err, av_err2str(ret)); + return ret; + } + + return 0; +} + +static av_cold int oh_decode_init(AVCodecContext *avctx) +{ + OHCodecDecContext *s = avctx->priv_data; + + // Initialize these fields first, so oh_decode_close can destroy them safely + ff_mutex_init(&s->input_mutex, NULL); + ff_cond_init(&s->input_cond, NULL); + ff_mutex_init(&s->output_mutex, NULL); + ff_cond_init(&s->output_cond, NULL); + + int ret = oh_decode_create(s, avctx); + if (ret < 0) + return ret; + ret = oh_decode_set_format(s, avctx); + if (ret < 0) + return ret; + + size_t fifo_size = 16; + s->input_queue = av_fifo_alloc2(fifo_size, sizeof(OHBufferQueueItem), + AV_FIFO_FLAG_AUTO_GROW); + s->output_queue = av_fifo_alloc2(fifo_size, sizeof(OHBufferQueueItem), + AV_FIFO_FLAG_AUTO_GROW); + if (!s->input_queue || !s->output_queue) + return AVERROR(ENOMEM); + + ret = oh_decode_start(s, avctx); + if (ret < 0) + return ret; + + return 0; +} + +static av_cold int oh_decode_close(AVCodecContext *avctx) +{ + OHCodecDecContext *s = avctx->priv_data; + + if (s->dec) { + /* Stop but don't destroy dec directly, to keep hardware frames on + * the fly valid. + */ + OH_AVErrCode err = OH_VideoDecoder_Stop(s->dec); + if (err == AV_ERR_OK) + av_log(NULL, AV_LOG_DEBUG, "Stop decoder success\n"); + else + av_log(NULL, AV_LOG_ERROR, "Stop decoder failed, %d, %s\n", + err, av_err2str(ff_oh_err_to_ff_err(err))); + s->dec = NULL; + av_buffer_unref(&s->dec_ref); + } + + av_packet_unref(&s->pkt); + + ff_mutex_destroy(&s->input_mutex); + ff_cond_destroy(&s->input_cond); + av_fifo_freep2(&s->input_queue); + + ff_mutex_destroy(&s->output_mutex); + ff_cond_destroy(&s->output_cond); + av_fifo_freep2(&s->output_queue); + + return 0; +} + +static void oh_buffer_release(void *opaque, uint8_t *data) +{ + if (!opaque) + return; + + OHCodecBuffer *buffer = opaque; + + if (!buffer->dec_ref) { + av_free(buffer); + return; + } + + if (buffer->buffer) { + OH_AVCodec *dec = (OH_AVCodec *)buffer->dec_ref->data; + OH_AVCodecBufferAttr attr; + OH_AVErrCode err = OH_AVBuffer_GetBufferAttr(buffer->buffer, &attr); + if (err == AV_ERR_OK && !(attr.flags & AVCODEC_BUFFER_FLAGS_DISCARD)) + OH_VideoDecoder_RenderOutputBuffer(dec, buffer->index); + else + OH_VideoDecoder_FreeOutputBuffer(dec, buffer->index); + } + + av_buffer_unref(&buffer->dec_ref); + av_free(buffer); +} + +static int oh_decode_wrap_hw_buffer(AVCodecContext *avctx, AVFrame *frame, + OHBufferQueueItem *output, + const OH_AVCodecBufferAttr *attr) +{ + OHCodecDecContext *s = avctx->priv_data; + + frame->width = s->width; + frame->height = s->height; + int ret = ff_decode_frame_props(avctx, frame); + if (ret < 0) + return ret; + + frame->format = AV_PIX_FMT_OHCODEC; + OHCodecBuffer *buffer = av_mallocz(sizeof(*buffer)); + if (!buffer) + return AVERROR(ENOMEM); + + buffer->dec_ref = av_buffer_ref(s->dec_ref); + if (!buffer->dec_ref) { + oh_buffer_release(buffer, NULL); + return AVERROR(ENOMEM); + } + + buffer->index = output->index; + buffer->buffer = output->buffer; + frame->buf[0] = av_buffer_create((uint8_t *)buffer->buffer, 1, + oh_buffer_release, + buffer, AV_BUFFER_FLAG_READONLY); + if (!frame->buf[0]) { + oh_buffer_release(buffer, NULL); + return AVERROR(ENOMEM); + } + // Point to OH_AVBuffer + frame->data[3] = frame->buf[0]->data; + frame->pts = av_rescale_q(attr->pts, AV_TIME_BASE_Q, avctx->pkt_timebase); + frame->pkt_dts = AV_NOPTS_VALUE; + + return 0; +} + +static int oh_decode_wrap_sw_buffer(AVCodecContext *avctx, AVFrame *frame, + OHBufferQueueItem *output, + const OH_AVCodecBufferAttr *attr) +{ + OHCodecDecContext *s = avctx->priv_data; + + frame->format = avctx->pix_fmt; + frame->width = s->width; + frame->height = s->height; + int ret = ff_get_buffer(avctx, frame, 0); + if (ret < 0) + return ret; + + frame->pts = av_rescale_q(attr->pts, AV_TIME_BASE_Q, avctx->pkt_timebase); + frame->pkt_dts = AV_NOPTS_VALUE; + + uint8_t *p = OH_AVBuffer_GetAddr(output->buffer); + if (!p) { + av_log(avctx, AV_LOG_ERROR, "Failed to get output buffer addr\n"); + return AVERROR_EXTERNAL; + } + + uint8_t *src[4] = {0}; + int src_linesizes[4] = {0}; + + ret = av_image_fill_linesizes(src_linesizes, frame->format, s->stride); + if (ret < 0) + return ret; + ret = av_image_fill_pointers(src, frame->format, s->slice_height, p, + src_linesizes); + if (ret < 0) + return ret; + av_image_copy2(frame->data, frame->linesize, src, src_linesizes, + frame->format, frame->width, frame->height); + + OH_AVErrCode err = OH_VideoDecoder_FreeOutputBuffer(s->dec, output->index); + if (err != AV_ERR_OK) { + ret = ff_oh_err_to_ff_err(err); + av_log(avctx, AV_LOG_ERROR, "FreeOutputBuffer failed, %d, %s\n", err, + av_err2str(ret)); + return ret; + } + + return 0; +} + +static int oh_decode_output_frame(AVCodecContext *avctx, AVFrame *frame, + OHBufferQueueItem *output) +{ + OHCodecDecContext *s = avctx->priv_data; + OH_AVCodecBufferAttr attr; + + OH_AVErrCode err = OH_AVBuffer_GetBufferAttr(output->buffer, &attr); + if (err != AV_ERR_OK) + return ff_oh_err_to_ff_err(err); + + if (attr.flags & AVCODEC_BUFFER_FLAGS_EOS) { + av_log(avctx, AV_LOG_DEBUG, "Buffer flag eos\n"); + OH_VideoDecoder_FreeOutputBuffer(s->dec, output->index); + return AVERROR_EOF; + } + + if (!s->got_stream_info) { + // This shouldn't happen, add a warning message. + av_log(avctx, AV_LOG_WARNING, + "decoder didn't notify stream info, try get format explicitly\n"); + + OH_AVFormat *format = OH_VideoDecoder_GetOutputDescription(s->dec); + if (!format) { + av_log(avctx, AV_LOG_ERROR, "GetOutputDescription failed\n"); + return AVERROR_EXTERNAL; + } + + oh_decode_on_stream_changed(s->dec, format, avctx); + OH_AVFormat_Destroy(format); + if (!s->got_stream_info) + return AVERROR_EXTERNAL; + } + + if (s->output_to_window) + return oh_decode_wrap_hw_buffer(avctx, frame, output, &attr); + return oh_decode_wrap_sw_buffer(avctx, frame, output, &attr); +} + +static int oh_decode_send_pkt(AVCodecContext *avctx, OHBufferQueueItem *input) +{ + OHCodecDecContext *s = avctx->priv_data; + OH_AVErrCode err; + int ret; + + if (!s->pkt.size && !s->eof_sent) { + OH_AVCodecBufferAttr attr = { + .flags = AVCODEC_BUFFER_FLAGS_EOS, + }; + err = OH_AVBuffer_SetBufferAttr(input->buffer, &attr); + if (err != AV_ERR_OK) + return ff_oh_err_to_ff_err(err); + err = OH_VideoDecoder_PushInputBuffer(s->dec, input->index); + if (err != AV_ERR_OK) + return ff_oh_err_to_ff_err(err); + s->eof_sent = true; + return 0; + } + + uint8_t *p = OH_AVBuffer_GetAddr(input->buffer); + int32_t n = OH_AVBuffer_GetCapacity(input->buffer); + if (!p || n <= 0) { + av_log(avctx, AV_LOG_ERROR, + "Failed to get buffer addr (%p) or capacity (%d)\n", + p, n); + return AVERROR_EXTERNAL; + } + n = FFMIN(s->pkt.size, n); + memcpy(p, s->pkt.data, n); + + OH_AVCodecBufferAttr attr = { + .size = n, + .offset = 0, + .pts = av_rescale_q(s->pkt.pts, avctx->pkt_timebase, + AV_TIME_BASE_Q), + .flags = (s->pkt.flags & AV_PKT_FLAG_KEY) + ? AVCODEC_BUFFER_FLAGS_SYNC_FRAME : 0, + }; + + err = OH_AVBuffer_SetBufferAttr(input->buffer, &attr); + if (err != AV_ERR_OK) { + ret = ff_oh_err_to_ff_err(err); + return ret; + } + err = OH_VideoDecoder_PushInputBuffer(s->dec, input->index); + if (err != AV_ERR_OK) { + ret = ff_oh_err_to_ff_err(err); + av_log(avctx, AV_LOG_ERROR, "Push input buffer failed, %d, %s\n", + err, av_err2str(ret)); + return ret; + } + + if (n < s->pkt.size) { + s->pkt.size -= n; + s->pkt.data += n; + } else { + av_packet_unref(&s->pkt); + } + + return 0; +} + +static int oh_decode_receive_frame(AVCodecContext *avctx, AVFrame *frame) +{ + OHCodecDecContext *s = avctx->priv_data; + + while (1) { + OHBufferQueueItem buffer = {0}; + int ret; + + // Try get output + ff_mutex_lock(&s->output_mutex); + while (!s->decode_status) { + if (av_fifo_read(s->output_queue, &buffer, 1) >= 0) + break; + // Only wait after send EOF + if (s->eof_sent && !s->decode_status) + ff_cond_wait(&s->output_cond, &s->output_mutex); + else + break; + } + + ret = s->decode_status; + ff_mutex_unlock(&s->output_mutex); + + // Got a frame + if (buffer.buffer) + return oh_decode_output_frame(avctx, frame, &buffer); + if (ret < 0) + return ret; + + if (!s->pkt.size) { + /* fetch new packet or eof */ + ret = ff_decode_get_packet(avctx, &s->pkt); + if (ret < 0 && ret != AVERROR_EOF) + return ret; + } + + // Wait input buffer + ff_mutex_lock(&s->input_mutex); + while (!s->decode_status) { + if (av_fifo_read(s->input_queue, &buffer, 1) >= 0) + break; + ff_cond_wait(&s->input_cond, &s->input_mutex); + } + + ret = s->decode_status; + ff_mutex_unlock(&s->input_mutex); + + if (ret < 0) + return ret; + + ret = oh_decode_send_pkt(avctx, &buffer); + if (ret < 0) + return ret; + } + + return AVERROR(EAGAIN); +} + +static void oh_decode_flush(AVCodecContext *avctx) +{ + OHCodecDecContext *s = avctx->priv_data; + + OH_VideoDecoder_Flush(s->dec); + + ff_mutex_lock(&s->input_mutex); + ff_mutex_lock(&s->output_mutex); + av_fifo_reset2(s->input_queue); + av_fifo_reset2(s->output_queue); + s->decode_status = 0; + s->eof_sent = false; + ff_mutex_unlock(&s->output_mutex); + ff_mutex_unlock(&s->input_mutex); + + OH_VideoDecoder_Start(s->dec); +} + +static const AVCodecHWConfigInternal *const oh_hw_configs[] = { + &(const AVCodecHWConfigInternal) { + .public = { + .pix_fmt = AV_PIX_FMT_OHCODEC, + .methods = AV_CODEC_HW_CONFIG_METHOD_AD_HOC | + AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, + .device_type = AV_HWDEVICE_TYPE_OHCODEC, + }, + .hwaccel = NULL, + }, + NULL +}; + +#define OFFSET(x) offsetof(OHCodecDecContext, x) +#define VD (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM) +static const AVOption ohcodec_vdec_options[] = { + {"codec_name", "Select codec by name", + OFFSET(name), AV_OPT_TYPE_STRING, .flags = VD}, + {"allow_sw", "Allow software decoding", + OFFSET(allow_sw), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, VD}, + {NULL} +}; + +#define DECLARE_OHCODEC_VCLASS(short_name) \ + static const AVClass short_name##_oh_dec_class = { \ + .class_name = #short_name "_ohcodec", \ + .item_name = av_default_item_name, \ + .option = ohcodec_vdec_options, \ + .version = LIBAVUTIL_VERSION_INT, \ + }; + +#define DECLARE_OHCODEC_VDEC(short_name, full_name, codec_id, bsf) \ + DECLARE_OHCODEC_VCLASS(short_name) \ + const FFCodec ff_##short_name##_oh_decoder = { \ + .p.name = #short_name "_ohcodec", \ + CODEC_LONG_NAME(full_name " OpenHarmony Codec"), \ + .p.type = AVMEDIA_TYPE_VIDEO, \ + .p.id = codec_id, \ + .p.priv_class = &short_name##_oh_dec_class, \ + .priv_data_size = sizeof(OHCodecDecContext), \ + .init = oh_decode_init, \ + FF_CODEC_RECEIVE_FRAME_CB(oh_decode_receive_frame), \ + .flush = oh_decode_flush, \ + .close = oh_decode_close, \ + .p.capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AVOID_PROBING | \ + AV_CODEC_CAP_HARDWARE, \ + .caps_internal = FF_CODEC_CAP_INIT_CLEANUP, \ + .bsfs = bsf, \ + .hw_configs = oh_hw_configs, \ + .p.wrapper_name = "ohcodec", \ + }; + +#if CONFIG_H264_OH_DECODER +DECLARE_OHCODEC_VDEC(h264, "H.264", AV_CODEC_ID_H264, "h264_mp4toannexb") +#endif + +#if CONFIG_HEVC_OH_DECODER +DECLARE_OHCODEC_VDEC(hevc, "H.265", AV_CODEC_ID_HEVC, "hevc_mp4toannexb") +#endif diff --git a/libavcodec/version.h b/libavcodec/version.h index 06631ffa8c..7aa95fc3f1 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -29,7 +29,7 @@ #include "version_major.h" -#define LIBAVCODEC_VERSION_MINOR 4 +#define LIBAVCODEC_VERSION_MINOR 5 #define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ -- 2.46.0 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
parent reply other threads:[~2025-07-01 12:53 UTC|newest] Thread overview: expand[flat|nested] mbox.gz Atom feed [parent not found: <20250701125257.14490-1-quinkblack@foxmail.com>]
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=tencent_C026C427C82A90D33F1C68CB86262FD08E06@qq.com \ --to=quinkblack-at-foxmail.com@ffmpeg.org \ --cc=ffmpeg-devel@ffmpeg.org \ --cc=zhilizhao@tencent.com \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel This inbox may be cloned and mirrored by anyone: git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \ ffmpegdev@gitmailbox.com public-inbox-index ffmpegdev Example config snippet for mirrors. AGPL code for this site: git clone https://public-inbox.org/public-inbox.git