- Added APV encoder wrapper - Changes in project configuration file and libavcodec Makefile - Added documentation for oapv wrapper
Signed-off-by: Dawid Kozinski <d.kozin...@samsung.com> --- configure | 4 + doc/encoders.texi | 41 +++ doc/general_contents.texi | 11 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 2 + libavcodec/apv_imgb.c | 332 ++++++++++++++++++++ libavcodec/apv_imgb.h | 34 +++ libavcodec/libapvenc.c | 617 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 1042 insertions(+) create mode 100644 libavcodec/apv_imgb.c create mode 100644 libavcodec/apv_imgb.h create mode 100644 libavcodec/libapvenc.c diff --git a/configure b/configure index 294f7e4a9d..fa125d383d 100755 --- a/configure +++ b/configure @@ -214,6 +214,7 @@ External library support: --enable-ladspa enable LADSPA audio filtering [no] --enable-lcms2 enable ICC profile support via LittleCMS 2 [no] --enable-libaom enable AV1 video encoding/decoding via libaom [no] + --enable-liboapv enable APV encoding/decoding via liboapv [no] --enable-libaribb24 enable ARIB text and caption decoding via libaribb24 [no] --enable-libaribcaption enable ARIB text and caption decoding via libaribcaption [no] --enable-libass enable libass subtitles rendering, @@ -1923,6 +1924,7 @@ EXTERNAL_LIBRARY_LIST=" ladspa lcms2 libaom + liboapv libaribcaption libass libbluray @@ -3559,6 +3561,7 @@ prores_videotoolbox_encoder_select="videotoolbox_encoder" libaom_av1_decoder_deps="libaom" libaom_av1_encoder_deps="libaom" libaom_av1_encoder_select="extract_extradata_bsf dovi_rpuenc" +libapv_encoder_deps="liboapv" libaribb24_decoder_deps="libaribb24" libaribcaption_decoder_deps="libaribcaption" libcelt_decoder_deps="libcelt" @@ -6928,6 +6931,7 @@ enabled jni && { [ $target_os = "android" ] && check_headers jni.h enabled ladspa && require_headers "ladspa.h dlfcn.h" enabled lcms2 && require_pkg_config lcms2 "lcms2 >= 2.13" lcms2.h cmsCreateContext enabled libaom && require_pkg_config libaom "aom >= 2.0.0" aom/aom_codec.h aom_codec_version +enabled liboapv && require_pkg_config liboapv "oapv >= 0.1.13" "oapv/oapv.h" oapve_encode enabled libaribb24 && { check_pkg_config libaribb24 "aribb24 > 1.0.3" "aribb24/aribb24.h" arib_instance_new || { enabled gpl && require_pkg_config libaribb24 aribb24 "aribb24/aribb24.h" arib_instance_new; } || die "ERROR: libaribb24 requires version higher than 1.0.3 or --enable-gpl."; } diff --git a/doc/encoders.texi b/doc/encoders.texi index 128e81a2e7..f5d6d69246 100644 --- a/doc/encoders.texi +++ b/doc/encoders.texi @@ -1889,6 +1889,47 @@ ffmpeg -i input -c:v libaom-av1 -b:v 500K -aom-params tune=psnr:enable-tpl-model @end table +@section liboapv + +Advanced Professional Video codec encoder wrapper. + +This encoder requires the presence of the liboapv headers and library +during configuration. You need to explicitly configure the build with +@option{--enable-liboapv}. + +@float NOTE +Many liboapv encoder options are mapped to FFmpeg global codec options, +while unique encoder options are provided through private options. +Additionally the apv-params private options allows one to pass a list +of key=value tuples as accepted by the liboapv @code{parse_apv_params} function. +@end float + +The apv project website is at @url{https://github.com/AcademySoftwareFoundation/openapv}. + +@subsection Options + +The following options are supported by the liboapv wrapper. +The apv-equivalent options or values are listed in parentheses for easy migration. + +@float NOTE +To reduce the duplication of documentation, only the private options +and some others requiring special attention are documented here. For +the documentation of the undocumented generic options, see +@ref{codec-options,,the Codec Options chapter}. +@end float + +@float NOTE +To get a more accurate and extensive documentation of the liboapv options, +invoke the command @code{apv_app_enc --help} or consult the liboapv documentation. +@end float + +@table @option +@item b (@emph{bitrate}) +Set target video bitrate in bits/s. +Note that FFmpeg's b option is expressed in bits/s, while apv's bitrate is in kilobits/s. + +@end table + @section libsvtav1 SVT-AV1 encoder wrapper. diff --git a/doc/general_contents.texi b/doc/general_contents.texi index 5faf89815b..66fe245050 100644 --- a/doc/general_contents.texi +++ b/doc/general_contents.texi @@ -35,6 +35,14 @@ package(amdgru-pro contains, but does not install automatically) are required. This driver can be installed using amdgpu-pro-install script in official amd driver archive. +@section Advanced Professional Video codec (APV) + +FFmpeg can make use of the APV library for APV video encoding and decoding. + +Go to @url{https://github.com/AcademySoftwareFoundation/openapv} and follow the instructions for +installing the APV library. Then pass @code{--enable-liboapv} to configure to +enable it. + @section AviSynth FFmpeg can read AviSynth scripts as input. To enable support, pass @@ -627,6 +635,7 @@ library: @item raw APAC @tab @tab X @item raw aptX @tab X @tab X @item raw aptX HD @tab X @tab X +@item raw apv @tab X @tab X @item raw Bonk @tab @tab X @item raw Chinese AVS video @tab X @tab X @item raw DFPWM @tab X @tab X @@ -895,6 +904,8 @@ following image formats are supported: @tab fourcc: apch,apcn,apcs,apco,ap4h,ap4x @item Apple QuickDraw @tab @tab X @tab fourcc: qdrw +@item Advanced Professional Video codec @tab X @tab X + @tab encoding and decoding supported through external library liboapv @item Argonaut Video @tab @tab X @tab Used in some Argonaut games. @item Asus v1 @tab X @tab X diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 3ae6dd17ea..7350f4ce07 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -1129,6 +1129,7 @@ OBJS-$(CONFIG_PCM_ALAW_AT_ENCODER) += audiotoolboxenc.o OBJS-$(CONFIG_PCM_MULAW_AT_ENCODER) += audiotoolboxenc.o OBJS-$(CONFIG_LIBAOM_AV1_DECODER) += libaomdec.o libaom.o OBJS-$(CONFIG_LIBAOM_AV1_ENCODER) += libaomenc.o libaom.o +OBJS-$(CONFIG_LIBAPV_ENCODER) += libapvenc.o apv_imgb.o OBJS-$(CONFIG_LIBARIBB24_DECODER) += libaribb24.o ass.o OBJS-$(CONFIG_LIBARIBCAPTION_DECODER) += libaribcaption.o ass.o OBJS-$(CONFIG_LIBCELT_DECODER) += libcelt_dec.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index f10519617e..4aec580366 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -763,6 +763,8 @@ extern const FFCodec ff_pcm_mulaw_at_decoder; extern const FFCodec ff_qdmc_at_decoder; extern const FFCodec ff_qdm2_at_decoder; extern FFCodec ff_libaom_av1_encoder; +extern const FFCodec ff_libapv_encoder; + /* preferred over libaribb24 */ extern const FFCodec ff_libaribcaption_decoder; extern const FFCodec ff_libaribb24_decoder; diff --git a/libavcodec/apv_imgb.c b/libavcodec/apv_imgb.c new file mode 100644 index 0000000000..967b512e35 --- /dev/null +++ b/libavcodec/apv_imgb.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2025 Dawid Kozinski <d.kozin...@samsung.com> + * + * This file is part of FFmpeg. + * + * 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 <libavcodec/avcodec.h> + +/* assert function */ +#include <assert.h> + +#include "apv_imgb.h" + +#if defined(_MSC_VER) // Microsoft Visual C++ +#include <intrin.h> +#define apv_atomic_inc(pcnt) _InterlockedIncrement(pcnt) +#define apv_atomic_dec(pcnt) _InterlockedDecrement(pcnt) + +#elif defined(__GNUC__) || defined(__clang__) // GCC i Clang +#define apv_atomic_inc(pcnt) __sync_fetch_and_add(pcnt, 1) + 1 +#define apv_atomic_dec(pcnt) __sync_fetch_and_sub(pcnt, 1) - 1 + +#else // In other cases, use mutexes +#include <pthread.h> +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +static int apv_atomic_inc(volatile int* pcnt) { + pthread_mutex_lock(&lock); + int new_value = ++(*pcnt); + pthread_mutex_unlock(&lock); + return new_value; +} + +static int apv_atomic_dec(volatile int* pcnt) { + pthread_mutex_lock(&lock); + int new_value = --(*pcnt); + pthread_mutex_unlock(&lock); + return new_value; +} +#endif + +#define assert_rv(x,r) {if(!(x)){assert(x); return (r);}} + +#define OAPV_IMG_CLIP_VAL(n, min, max) (((n) > (max)) ? (max) : (((n) < (min)) ? (min) : (n))) +#define OAPV_IMG_ALIGN_VAL(val, align) ((((val) + (align) - 1) / (align)) * (align)) + +static void * apv_picbuf_alloc(int size) +{ + return malloc(size); +} + +static void apv_picbuf_free(void* p) +{ + if (p) {free(p);} +} + +static void apv_imgb_cpy_plane(oapv_imgb_t *dst, oapv_imgb_t *src) +{ + int i, j; + unsigned char *s, *d; + int numbyte = OAPV_CS_GET_BYTE_DEPTH(src->cs); + + for(i = 0; i < src->np; i++) { + s = (unsigned char *)src->a[i]; + d = (unsigned char *)dst->a[i]; + + for(j = 0; j < src->ah[i]; j++) { + memcpy(d, s, numbyte * src->aw[i]); + s += src->s[i]; + d += dst->s[i]; + } + } +} + +static void apv_imgb_cpy_shift_left_8b(oapv_imgb_t *dst, oapv_imgb_t *src, int shift) +{ + int i, j, k; + + unsigned char *s; + short *d; + + for(i = 0; i < dst->np; i++) { + s = (unsigned char *)src->a[i]; + d = (short *)dst->a[i]; + + for(j = 0; j < src->ah[i]; j++) { + for(k = 0; k < src->aw[i]; k++) { + d[k] = (short)(s[k] << shift); + } + s = s + src->s[i]; + d = (short *)(((unsigned char *)d) + dst->s[i]); + } + } +} + +static void apv_imgb_cpy_shift_right_8b(oapv_imgb_t *dst, oapv_imgb_t *src, int shift) +{ + int i, j, k, t0, add; + + short *s; + unsigned char *d; + + if(shift) + add = 1 << (shift - 1); + else + add = 0; + + for(i = 0; i < dst->np; i++) { + s = (short *)src->a[i]; + d = (unsigned char *)dst->a[i]; + + for(j = 0; j < src->ah[i]; j++) { + for(k = 0; k < src->aw[i]; k++) { + t0 = ((s[k] + add) >> shift); + d[k] = (unsigned char)(OAPV_IMG_CLIP_VAL(t0, 0, 255)); + } + s = (short *)(((unsigned char *)s) + src->s[i]); + d = d + dst->s[i]; + } + } +} + +static void apv_imgb_cpy_shift_left(oapv_imgb_t *dst, oapv_imgb_t *src, int shift) +{ + int i, j, k; + + unsigned short *s; + unsigned short *d; + + for(i = 0; i < dst->np; i++) { + s = (unsigned short *)src->a[i]; + d = (unsigned short *)dst->a[i]; + + for(j = 0; j < src->h[i]; j++) { + for(k = 0; k < src->w[i]; k++) { + d[k] = (unsigned short)(s[k] << shift); + } + s = (unsigned short *)(((unsigned char *)s) + src->s[i]); + d = (unsigned short *)(((unsigned char *)d) + dst->s[i]); + } + } +} + +static void apv_imgb_cpy_shift_right(oapv_imgb_t *dst, oapv_imgb_t *src, int shift) +{ + int i, j, k, t0, add; + + int clip_min = 0; + int clip_max = 0; + + unsigned short *s; + unsigned short *d; + + if(shift) + add = 1 << (shift - 1); + else + add = 0; + + clip_max = (1 << (OAPV_CS_GET_BIT_DEPTH(dst->cs))) - 1; + + for(i = 0; i < dst->np; i++) { + s = (unsigned short *)src->a[i]; + d = (unsigned short *)dst->a[i]; + + for(j = 0; j < src->h[i]; j++) { + for(k = 0; k < src->w[i]; k++) { + t0 = ((s[k] + add) >> shift); + d[k] = (OAPV_IMG_CLIP_VAL(t0, clip_min, clip_max)); + } + s = (unsigned short *)(((unsigned char *)s) + src->s[i]); + d = (unsigned short *)(((unsigned char *)d) + dst->s[i]); + } + } +} + +oapv_imgb_t * apv_imgb_create(int w, int h, int cs, AVCodecContext *avctx) +{ + int i, bd; + oapv_imgb_t * imgb; + + imgb = (oapv_imgb_t *)malloc(sizeof(oapv_imgb_t)); + if(imgb == NULL) goto ERR; + memset(imgb, 0, sizeof(oapv_imgb_t)); + + bd = OAPV_CS_GET_BYTE_DEPTH(cs); /* byte unit */ + + imgb->w[0] = w; + imgb->h[0] = h; + switch(OAPV_CS_GET_FORMAT(cs)) + { + case OAPV_CF_YCBCR400: + imgb->w[1] = imgb->w[2] = w; + imgb->h[1] = imgb->h[2] = h; + imgb->np = 1; + break; + case OAPV_CF_YCBCR420: + imgb->w[1] = imgb->w[2] = (w + 1) >> 1; + imgb->h[1] = imgb->h[2] = (h + 1) >> 1; + imgb->np = 3; + break; + case OAPV_CF_YCBCR422: + imgb->w[1] = imgb->w[2] = (w + 1) >> 1; + imgb->h[1] = imgb->h[2] = h; + imgb->np = 3; + break; + case OAPV_CF_YCBCR444: + imgb->w[1] = imgb->w[2] = w; + imgb->h[1] = imgb->h[2] = h; + imgb->np = 3; + break; + case OAPV_CF_YCBCR4444: + imgb->w[1] = imgb->w[2] = imgb->w[3] = w; + imgb->h[1] = imgb->h[2] = imgb->h[3] = h; + imgb->np = 4; + break; + case OAPV_CF_PLANAR2: + imgb->w[1] = w; + imgb->h[1] = h; + imgb->np = 2; + break; + default: + av_log(avctx, AV_LOG_ERROR, "unsupported color format\n"); + goto ERR; + } + + for(i = 0; i < imgb->np; i++) + { + imgb->aw[i] = OAPV_IMG_ALIGN_VAL(imgb->w[i], OAPV_MB_W); + imgb->s[i] = imgb->aw[i] * bd; + imgb->ah[i] = OAPV_IMG_ALIGN_VAL(imgb->h[i], OAPV_MB_H); + imgb->e[i] = imgb->ah[i]; + + imgb->bsize[i] = imgb->s[i] * imgb->e[i]; + imgb->a[i] = imgb->baddr[i] = apv_picbuf_alloc(imgb->bsize[i]); + if(imgb->a[i] == NULL) goto ERR; + + memset(imgb->a[i], 0, imgb->bsize[i]); + } + imgb->cs = cs; + imgb->addref = apv_imgb_addref; + imgb->getref = apv_imgb_getref; + imgb->release = apv_imgb_release; + + imgb->addref(imgb); /* increase reference count */ + return imgb; + +ERR: + av_log(avctx, AV_LOG_ERROR, "cannot create image buffer\n"); + if(imgb) + { + for (int i = 0; i < OAPV_MAX_CC; i++) + { + if(imgb->a[i]) free(imgb->a[i]); + } + free(imgb); + } + return NULL; +} + +int apv_imgb_release(oapv_imgb_t * imgb) +{ + int refcnt, i; + assert_rv(imgb, OAPV_ERR_INVALID_ARGUMENT); + refcnt = apv_atomic_dec(&imgb->refcnt); + if(refcnt == 0) { + for(i=0; i<OAPV_MAX_CC; i++) { + if (imgb->baddr[i]) apv_picbuf_free(imgb->baddr[i]); + } + free(imgb); + } + + return refcnt; +} + +void apv_imgb_cpy(oapv_imgb_t *dst, oapv_imgb_t *src, AVCodecContext *avctx) +{ + int i, bd_src, bd_dst; + bd_src = OAPV_CS_GET_BIT_DEPTH(src->cs); + bd_dst = OAPV_CS_GET_BIT_DEPTH(dst->cs); + + if(src->cs == dst->cs) { + apv_imgb_cpy_plane(dst, src); + } + else if(bd_src == 8 && bd_dst > 8) { + apv_imgb_cpy_shift_left_8b(dst, src, bd_dst - bd_src); + } + else if(bd_src > 8 && bd_dst == 8) { + apv_imgb_cpy_shift_right_8b(dst, src, bd_src - bd_dst); + } + else if(bd_src < bd_dst) { + apv_imgb_cpy_shift_left(dst, src, bd_dst - bd_src); + } + else if(bd_src > bd_dst) { + apv_imgb_cpy_shift_right(dst, src, bd_src - bd_dst); + } + else { + av_log(avctx, AV_LOG_ERROR, "ERROR: unsupported image copy\n"); + return; + } + for(i = 0; i < OAPV_MAX_CC; i++) { + dst->x[i] = src->x[i]; + dst->y[i] = src->y[i]; + dst->w[i] = src->w[i]; + dst->h[i] = src->h[i]; + dst->ts[i] = src->ts[i]; + } +} + +int apv_imgb_addref(oapv_imgb_t * imgb) +{ + assert_rv(imgb, OAPV_ERR_INVALID_ARGUMENT); + return apv_atomic_inc(&imgb->refcnt); +} + +int apv_imgb_getref(oapv_imgb_t * imgb) +{ + assert_rv(imgb, OAPV_ERR_INVALID_ARGUMENT); + return imgb->refcnt; +} \ No newline at end of file diff --git a/libavcodec/apv_imgb.h b/libavcodec/apv_imgb.h new file mode 100644 index 0000000000..fe12bb228d --- /dev/null +++ b/libavcodec/apv_imgb.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Dawid Kozinski <d.kozin...@samsung.com> + * + * This file is part of FFmpeg. + * + * 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_APV_IMGB_H +#define AVCODEC_APV_IMGB_H + +#include <oapv/oapv.h> + +oapv_imgb_t* apv_imgb_create(int w, int h, int cs, AVCodecContext *avctx); +int apv_imgb_release(oapv_imgb_t* imgb); + +void apv_imgb_cpy(oapv_imgb_t *dst, oapv_imgb_t *src, AVCodecContext *avctx); + +int apv_imgb_addref(oapv_imgb_t* imgb); +int apv_imgb_getref(oapv_imgb_t* imgb); + +#endif // AVCODEC_APV_IMGB_H diff --git a/libavcodec/libapvenc.c b/libavcodec/libapvenc.c new file mode 100644 index 0000000000..819cd6c310 --- /dev/null +++ b/libavcodec/libapvenc.c @@ -0,0 +1,617 @@ +/* + * liboapv encoder + * Advanced Professional Video codec library + * + * Copyright (C) 2025 Dawid Kozinski <d.kozin...@samsung.com> + * + * This file is part of FFmpeg. + * + * 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 <float.h> +#include <stdlib.h> + +#include <oapv/oapv.h> + +#include "libavutil/internal.h" +#include "libavutil/common.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/pixfmt.h" +#include "libavutil/time.h" +#include "libavutil/cpu.h" +#include "libavutil/avstring.h" +#include "libavutil/mem.h" +#include "libavutil/avassert.h" +#include <libavutil/imgutils.h> + +#include "avcodec.h" +#include "internal.h" +#include "packet_internal.h" +#include "codec_internal.h" +#include "profiles.h" +#include "encode.h" +#include "apv_imgb.h" + +#define MAX_BS_BUF (128 * 1024 * 1024) +#define MAX_NUM_FRMS (1) // supports only 1-frame in an access unit +#define FRM_IDX (0) // supports only 1-frame in an access unit +#define MAX_NUM_CC (OAPV_MAX_CC) // Max number of color componets (upto 4:4:4:4) + +/** + * The structure stores all the states associated with the instance of APV encoder + */ +typedef struct ApvEncContext { + const AVClass *class; + + oapve_t id; // APV instance identifier + oapvm_t mid; + oapve_cdesc_t cdsc; // coding parameters i.e profile, width & height of input frame, num of therads, frame rate ... + oapv_bitb_t bitb; // bitstream buffer (output) + oapve_stat_t stat; // encoding status (output) + + oapv_imgb_t *imgb_r; // image buffer for read + oapv_imgb_t *imgb_i; // image buffer for input + oapv_frms_t ifrms; // frames for input + + int num_frames; // number of frames in an access unit + + int preset_id; // preset of apv ( fastest, fast, medium, slow, placebo) + + int qp; // quantization parameter (QP) [0,51] + + int input_depth; // input data bit depth (8, 10) + int input_csp; // input data color space (chroma format) + // - 0: YUV400 + // - 1: YUV420 + // - 2: YUV422 + // - 3: YUV444 + + AVDictionary *oapv_params; +} ApvEncContext; + +static int align_to_16(int value) { + return (value + 15) & ~15; // Rounding to the nearest value divisible by 16 +} + +static AVFrame* copy_and_align_avframe_to_16(const AVFrame* src_frame) { + AVFrame* dst_frame = NULL; + int ret = 0; + + if (!src_frame) { + return NULL; + } + + // Allocate a new AVFrame + dst_frame = av_frame_alloc(); + if (!dst_frame) { + return NULL; + } + + // Set parameters for the new frame + dst_frame->format = src_frame->format; + dst_frame->width = align_to_16(src_frame->width); + dst_frame->height = align_to_16(src_frame->height); + + // Memory allocation for a new frame + ret = av_frame_get_buffer(dst_frame, 16); + if (ret < 0) { + av_frame_free(&dst_frame); + return NULL; + } + + // Data copying + av_frame_copy(dst_frame, src_frame); + + return dst_frame; +} + +/** + * Convert FFmpeg pixel format (AVPixelFormat) into APV pre-defined color format + * + * @param[in] px_fmt pixel format (@see https://ffmpeg.org/doxygen/trunk/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5) + * + * @return APV pre-defined color format (@see oapv.h) on success, OAPV_CF_UNKNOWN on failure + */ +static int libapve_apv_color_format(enum AVPixelFormat av_pix_fmt) +{ + int cf = OAPV_CF_UNKNOWN; + + switch (av_pix_fmt) { + case AV_PIX_FMT_GRAY10: + cf = OAPV_CF_YCBCR400; + break; + case AV_PIX_FMT_GRAY12: + cf = OAPV_CF_YCBCR400; + break; + case AV_PIX_FMT_YUV420P10: + cf = OAPV_CF_YCBCR420; + break; + case AV_PIX_FMT_YUV422P10: + cf = OAPV_CF_YCBCR422; + break; + case AV_PIX_FMT_YUV444P10: + cf = OAPV_CF_YCBCR444; + break; + case AV_PIX_FMT_YUV422P12: + cf = OAPV_CF_YCBCR422; + break; + case AV_PIX_FMT_YUV444P12: + cf = OAPV_CF_YCBCR444; + break; + case AV_PIX_FMT_YUVA444P10: + cf = OAPV_CF_YCBCR4444; + break; + case AV_PIX_FMT_YUVA444P12: + cf = OAPV_CF_YCBCR4444; + break; + default: + cf = OAPV_CF_UNKNOWN; + break; + } + + return cf; +} + +/** + * Convert FFmpeg pixel format (AVPixelFormat) into APV pre-defined color space + * + * @param[in] px_fmt pixel format (@see https://ffmpeg.org/doxygen/trunk/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5) + * + * @return APV pre-defined color space (@see oapv.h) on success, OAPV_CS_UNKNOWN on failure + */ +static int libapve_apv_color_space(enum AVPixelFormat av_pix_fmt) +{ + int cs = OAPV_CS_UNKNOWN; + + switch (av_pix_fmt) { + case AV_PIX_FMT_YUV422P10: + cs = OAPV_CS_SET(OAPV_CF_YCBCR422, 10, AV_HAVE_BIGENDIAN); + break; + case AV_PIX_FMT_YUV444P10: + cs = OAPV_CS_SET(OAPV_CF_YCBCR444, 10, AV_HAVE_BIGENDIAN); + break; + case AV_PIX_FMT_YUV422P12: + cs = OAPV_CS_SET(OAPV_CF_YCBCR422, 12, AV_HAVE_BIGENDIAN); + break; + case AV_PIX_FMT_YUV444P12: + cs = OAPV_CS_SET(OAPV_CF_YCBCR444, 12, AV_HAVE_BIGENDIAN); + break; + default: + cs = OAPV_CS_UNKNOWN; + break; + } + + return cs; +} + +/** + * The function returns a pointer to the object of the oapve_cdesc_t type. + * oapve_cdesc_t contains all encoder parameters that should be initialized before the encoder is used. + * + * The field values of the oapve_cdesc_t structure are populated based on: + * - the corresponding field values of the AvCodecConetxt structure, + * - the apv encoder specific option values, + * (the full list of options available for apv encoder is displayed after executing the command ./ffmpeg --help encoder = liboapv) + * + * The order of processing input data and populating the apve_cdsc structure + * 1) first, the fields of the AVCodecContext structure corresponding to the provided input options are processed, + * (i.e -pix_fmt yuv422p -s:v 1920x1080 -r 30 -profile:v 0) + * 2) then apve-specific options added as AVOption to the apv AVCodec implementation + * (i.e -preset 0) + * + * Keep in mind that, there are options that can be set in different ways. + * In this case, please follow the above-mentioned order of processing. + * The most recent assignments overwrite the previous values. + * + * @param[in] avctx codec context (AVCodecContext) + * @param[out] cdsc contains all APV encoder encoder parameters that should be initialized before the encoder is use + * + * @return 0 on success, negative error code on failure + */ +static int get_conf(AVCodecContext *avctx, oapve_cdesc_t *cdsc) +{ + ApvEncContext *apvctx = NULL; + int ret; + + const char *option_name = "qp"; + uint8_t qp_default_value = 0; + const AVOption *option = NULL; + + apvctx = avctx->priv_data; + option = av_opt_find(&apvctx->class, option_name, NULL, 0, 0); + + if (option) { + uint64_t default_value; + av_opt_get_int(avctx->priv_data, option_name, 0, &default_value); + + if (default_value) { + qp_default_value = (uint8_t)default_value; + } + } + + for(int i=0;i<OAPV_MAX_NUM_FRAMES;i++) { + + /* initialize apv_param struct with default values */ + ret = oapve_param_default(&cdsc->param[i]); + if (OAPV_FAILED(ret)) { + av_log(avctx, AV_LOG_ERROR, "Cannot set default parameter\n"); + return AVERROR_EXTERNAL; + } + + /* read options from AVCodecContext */ + if (avctx->width > 0) + cdsc->param[i].w = avctx->width; + + if (avctx->height > 0) + cdsc->param[i].h = avctx->height; + + if (avctx->framerate.num > 0) { + cdsc->param[i].fps_num = avctx->framerate.num; + cdsc->param[i].fps_den = avctx->framerate.den; + } + + cdsc->param[i].preset = apvctx->preset_id; + cdsc->param[i].qp = apvctx->qp; + if (avctx->bit_rate / 1000 > INT_MAX || avctx->rc_max_rate / 1000 > INT_MAX) { + av_log(avctx, AV_LOG_ERROR, "Not supported bitrate bit_rate and rc_max_rate > %d000\n", INT_MAX); + return AVERROR_INVALIDDATA; + } + cdsc->param[i].bitrate = (int)(avctx->bit_rate / 1000); + if(cdsc->param[i].bitrate) { + if(cdsc->param[i].qp!=qp_default_value) { + av_log(avctx, AV_LOG_WARNING, "You cannot set both the bitrate and the QP parameter at the same time.\n" + "If the bitrate is set, the rate control type is set to ABR, which means that the QP value is ignored.\n"); + } + cdsc->param[i].rc_type = OAPV_RC_ABR; + } + + cdsc->threads = OAPV_CDESC_THREADS_AUTO; + + if(avctx->color_primaries!=AVCOL_PRI_UNSPECIFIED) + cdsc->param[i].color_primaries = avctx->color_primaries; + + if(avctx->color_trc!=AVCOL_TRC_UNSPECIFIED) + cdsc->param[i].transfer_characteristics = avctx->color_trc; + + if(avctx->colorspace!=AVCOL_SPC_UNSPECIFIED) + cdsc->param[i].matrix_coefficients = avctx->colorspace; + + if(avctx->color_range!=AVCOL_RANGE_UNSPECIFIED) + cdsc->param[i].full_range_flag = (avctx->color_range==AVCOL_RANGE_JPEG)?1:0; + } + + apvctx->input_csp = libapve_apv_color_space(avctx->pix_fmt); + if(apvctx->input_csp == OAPV_CS_UNKNOWN) { + av_log(avctx, AV_LOG_ERROR, "Not supported pixel format: %s\n", av_get_pix_fmt_name (avctx->pix_fmt)); + return AVERROR_INVALIDDATA; + } + + cdsc->max_bs_buf_size = MAX_BS_BUF; /* maximum bitstream buffer size */ + cdsc->max_num_frms = MAX_NUM_FRMS; + + return 0; +} + +static int get_bit_depth(AVCodecContext *avctx, enum AVPixelFormat pixel_format) + { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pixel_format); + if (desc == NULL) { + av_log(avctx, AV_LOG_ERROR, "Unsupported pixel format (%s)\n", av_get_pix_fmt_name(pixel_format)); + return AVERROR_EXTERNAL; + } + return desc->comp[0].depth; + } + + +/** + * @brief Initialize APV codec + * Create an encoder instance and allocate all the needed resources + * + * @param avctx codec context + * @return 0 on success, negative error code on failure + */ +static av_cold int libapve_init(AVCodecContext *avctx) +{ + ApvEncContext *apvctx = avctx->priv_data; + unsigned char *bs_buf = NULL; + int cfmt = OAPV_CF_UNKNOWN; // color format + oapve_cdesc_t *cdsc = &(apvctx->cdsc); + int ret = 0; + + apvctx->id = NULL; + apvctx->mid = NULL; + apvctx->bitb.addr = NULL; + memset(cdsc, 0, sizeof(oapve_cdesc_t)); + + /* allocate bitstream buffer */ + bs_buf = (unsigned char *)av_malloc(MAX_BS_BUF); + if (bs_buf == NULL) { + av_log(avctx, AV_LOG_ERROR, "Cannot allocate bitstream buffer, size=%d\n", MAX_BS_BUF); + return AVERROR(ENOMEM); + } + apvctx->bitb.addr = bs_buf; + apvctx->bitb.bsize = MAX_BS_BUF; + + /* read configurations and set values for created descriptor (APV_CDSC) */ + if ((ret = get_conf(avctx, cdsc)) != 0) { + av_log(avctx, AV_LOG_ERROR, "Cannot get OAPV configuration\n"); + return AVERROR(EINVAL); + } + + { + const AVDictionaryEntry *en = NULL; + while (en = av_dict_iterate(apvctx->oapv_params, en)) { + for(int i=0; i<OAPV_MAX_NUM_FRAMES; i++) { + if ((ret = oapve_param_parse(&cdsc->param[i], en->key, en->value)) < 0) { + av_log(avctx, AV_LOG_WARNING, "Error parsing option '%s = %s'.\n", en->key, en->value); + } + } + } + } + + /* create encoder */ + apvctx->id = oapve_create(cdsc, &ret); + if (apvctx->id == NULL) { + av_log(avctx, AV_LOG_ERROR, "Cannot create OAPV encoder\n"); + if(ret==OAPV_ERR_INVALID_LEVEL) { + av_log(avctx, AV_LOG_ERROR, "Invalid level idc: %d\n", cdsc->param[0].level_idc); + } + return AVERROR_EXTERNAL; + } + + /* create metadata handler */ + apvctx->mid = oapvm_create(&ret); + if(apvctx->mid == NULL || OAPV_FAILED(ret)) { + av_log(avctx, AV_LOG_ERROR, "cannot create OAPV metadata handler\n"); + return AVERROR_EXTERNAL; + } + + apvctx->input_depth = get_bit_depth(avctx, avctx->pix_fmt); + if(apvctx->input_depth != 10 && apvctx->input_depth != 12) { + av_log(avctx, AV_LOG_ERROR, "Unsupported pixel format (%s)n", av_get_pix_fmt_name(avctx->pix_fmt)); + return AVERROR(EINVAL); + } + + apvctx->imgb_r = NULL; // image buffer for read + apvctx->imgb_i = NULL; // image buffer for input + apvctx->num_frames = MAX_NUM_FRMS; // number of frames in an access unit + + cfmt = libapve_apv_color_format(avctx->pix_fmt); + + // create input and reconstruction image buffers + memset(&apvctx->ifrms, 0, sizeof(oapv_frms_t)); + + for(int i = 0; i < apvctx->num_frames; i++) { + if(apvctx->input_depth == 10) { + apvctx->ifrms.frm[FRM_IDX].imgb = apv_imgb_create(avctx->width, avctx->height, OAPV_CS_SET(cfmt, apvctx->input_depth, 0), avctx); + } + else { + apvctx->imgb_r = apv_imgb_create(avctx->width, avctx->height, OAPV_CS_SET(cfmt, apvctx->input_depth, 0), avctx); + apvctx->ifrms.frm[FRM_IDX].imgb = apv_imgb_create(avctx->width, avctx->height, OAPV_CS_SET(cfmt, 10, 0), avctx); + } + apvctx->ifrms.num_frms++; + } + + /* color description values */ + if(cdsc->param[FRM_IDX].color_description_present_flag) { + avctx->color_primaries = cdsc->param[FRM_IDX].color_primaries; + avctx->color_trc = cdsc->param[FRM_IDX].transfer_characteristics; + avctx->colorspace = cdsc->param[FRM_IDX].matrix_coefficients; + avctx->color_range = (cdsc->param[FRM_IDX].full_range_flag)?AVCOL_RANGE_JPEG:AVCOL_RANGE_MPEG; + } + + return 0; +} + +/** + * Encode raw data frame into APV packet + * + * @param[in] avctx codec context + * @param[out] avpkt output AVPacket containing encoded data + * @param[in] frame AVFrame containing the raw data to be encoded + * @param[out] got_packet encoder sets to 0 or 1 to indicate that a + * non-empty packet was returned in pkt + * + * @return 0 on success, negative error code on failure + */ +static int libapve_encode(AVCodecContext *avctx, AVPacket *avpkt, + const AVFrame *frame, int *got_packet) +{ + ApvEncContext *apvctx = avctx->priv_data; + AVFrame* tmp_frame = NULL; + int ret = -1; + + if (frame==NULL) { + return 0; + } + + if(apvctx->input_depth == 10) { + apvctx->imgb_i = apvctx->ifrms.frm[FRM_IDX].imgb; + } + else { + apvctx->imgb_i = apvctx->imgb_r; + } + + // The liboapv library requires that the frame size be a multiple of 16 + tmp_frame = copy_and_align_avframe_to_16(frame); + for (int i = 0; i < apvctx->imgb_i->np; i++) { + memcpy(apvctx->imgb_i->a[i], tmp_frame->data[i], tmp_frame->linesize[i]*tmp_frame->height); + } + av_frame_free(&tmp_frame); + + // @todo Possibility of optimization (skipping memory allocation for an additional temporary object of type AVFrame). + // + // int h_chroma, v_chroma; + // int frame_height[4]; + + // av_pix_fmt_get_chroma_subsample(frame->format, &h_chroma, &v_chroma); + + // frame_height[0] = frame->height; + // frame_height[1] = frame->height / v_chroma; + // frame_height[2] = frame->height / v_chroma; + // frame_height[3] = 0; + + // for (int i = 0; i < apvctx->imgb_i->np; i++) { + // for(int j=0; j < frame_height[i]; j++) { + // memcpy(apvctx->imgb_i->a[i]+j*apvctx->imgb_i->s[i], frame->data[i]+j*frame->linesize[i], frame->linesize[i]); + // } + // } + + if(apvctx->input_depth != 10) { + apv_imgb_cpy(apvctx->ifrms.frm[FRM_IDX].imgb, apvctx->imgb_i, avctx); + } + + apvctx->ifrms.frm[FRM_IDX].imgb->ts[0] = frame->pts; + + apvctx->ifrms.frm[FRM_IDX].group_id = 1; // @todo FIX-ME : need to set properly in case of multi-frame + apvctx->ifrms.frm[FRM_IDX].pbu_type = OAPV_PBU_TYPE_PRIMARY_FRAME; + + ret = oapve_encode(apvctx->id, &apvctx->ifrms, apvctx->mid, &(apvctx->bitb), &(apvctx->stat), NULL); + if (OAPV_FAILED(ret)) { + av_log(avctx, AV_LOG_ERROR, "oapve_encode() failed\n"); + return AVERROR_EXTERNAL; + } + + /* store bitstream */ + if(OAPV_SUCCEEDED(ret)) { + if(apvctx->stat.write > 0) { + ret = ff_get_encode_buffer(avctx, avpkt, apvctx->stat.write, 0); + if (ret < 0) + return ret; + + memcpy(avpkt->data, apvctx->bitb.addr, apvctx->stat.write); + + avpkt->time_base.num = apvctx->cdsc.param->fps_num; + avpkt->time_base.den = apvctx->cdsc.param->fps_den; + + avpkt->pts = avpkt->dts = frame->pts; + avpkt->flags |= AV_PKT_FLAG_KEY; + + ff_side_data_set_encoder_stats(avpkt, apvctx->qp * FF_QP2LAMBDA, NULL, 0, AV_PICTURE_TYPE_I); + + *got_packet = 1; + } else { + *got_packet = 0; + } + } + + return 0; +} + +/** + * Destroy the encoder and release all the allocated resources + * + * @param avctx codec context + * @return 0 on success, negative error code on failure + */ +static av_cold int libapve_close(AVCodecContext *avctx) +{ + ApvEncContext *apvctx = avctx->priv_data; + (void)apvctx; + + if(apvctx->imgb_r != NULL) + apvctx->imgb_r->release(apvctx->imgb_r); + + for(int i = 0; i < apvctx->num_frames; i++) { + if(apvctx->ifrms.frm[i].imgb != NULL) { + apvctx->ifrms.frm[i].imgb->release(apvctx->ifrms.frm[i].imgb); + } + } + + if (apvctx->mid) { + oapvm_rem_all(apvctx->mid); + } + + if (apvctx->id) { + oapve_delete(apvctx->id); + apvctx->id = NULL; + } + + if (apvctx->mid) { + oapvm_delete(apvctx->mid); + apvctx->mid = NULL; + } + + av_free(apvctx->bitb.addr); /* release bitstream buffer */ + + return 0; +} + +#define OFFSET(x) offsetof(ApvEncContext, x) +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM + +static const enum AVPixelFormat supported_pixel_formats[] = { + AV_PIX_FMT_YUV422P10, + AV_PIX_FMT_YUV444P10, + AV_PIX_FMT_YUV422P12, + AV_PIX_FMT_YUV444P12, + AV_PIX_FMT_NONE +}; + +// Consider using following options (./ffmpeg --help encoder=liboapv) +// +// Using oapv-params: +// ffmpeg -f rawvideo -pix_fmt yuv422p10le -i ${INPUT_FILE} -c:v liboapv -oapv-params "profile=422-10:level=7.1:band=3:preset=medium:width=352:height=288:fps=24:qp=63:bitrate=1M" -f rawvideo ${OUTPUT_FILE} +// +static const AVOption liboapv_options[] = { + { "preset", "Encoding preset for setting encoding speed (optimization level control)", OFFSET(preset_id), AV_OPT_TYPE_INT, { .i64 = OAPV_PRESET_DEFAULT }, OAPV_PRESET_FASTEST, OAPV_PRESET_PLACEBO, VE, .unit = "preset" }, + { "fastest", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_FASTEST }, INT_MIN, INT_MAX, VE, .unit = "preset" }, + { "fast", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_FAST }, INT_MIN, INT_MAX, VE, .unit = "preset" }, + { "medium", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_MEDIUM }, INT_MIN, INT_MAX, VE, .unit = "preset" }, + { "slow", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_SLOW }, INT_MIN, INT_MAX, VE, .unit = "preset" }, + { "placebo", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_PLACEBO }, INT_MIN, INT_MAX, VE, .unit = "preset" }, + { "default", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_DEFAULT }, INT_MIN, INT_MAX, VE, .unit = "preset" }, + + { "qp", "Quantization parameter value for CQP rate control mode", OFFSET(qp), AV_OPT_TYPE_INT, { .i64 = 32 }, 0, 51, VE }, + { "oapv-params", "Override the apv configuration using a :-separated list of key=value parameters", OFFSET(oapv_params), AV_OPT_TYPE_DICT, { 0 }, 0, 0, VE }, + { NULL } +}; + +static const AVClass libapve_class = { + .class_name = "liboapv", + .item_name = av_default_item_name, + .option = liboapv_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +/** + * libavcodec generic global options, which can be set on all the encoders and decoders + * @see https://www.ffmpeg.org/ffmpeg-codecs.html#Codec-Options + */ +static const FFCodecDefault libapve_defaults[] = { + { "b", "0" }, // bitrate in terms of kilo-bits per second (support for bit-rates from a few hundred Mbps to a few Gbps for 2K, 4K and 8K resolution content) + { "threads", "0"}, // number of threads to be used (0: automatically select the number of threads to set) + { NULL }, +}; + +const FFCodec ff_libapv_encoder = { + .p.name = "liboapv", + .p.long_name = NULL_IF_CONFIG_SMALL("liboapv APV"), + .p.type = AVMEDIA_TYPE_VIDEO, + .p.id = AV_CODEC_ID_APV, + .init = libapve_init, + FF_CODEC_ENCODE_CB(libapve_encode), + .close = libapve_close, + .priv_data_size = sizeof(ApvEncContext), + .p.priv_class = &libapve_class, + .defaults = libapve_defaults, + .p.capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_OTHER_THREADS | AV_CODEC_CAP_DR1, + .p.wrapper_name = "liboapv", + .p.profiles = NULL_IF_CONFIG_SMALL(ff_apv_profiles), + .p.pix_fmts = supported_pixel_formats, + .caps_internal = FF_CODEC_CAP_INIT_CLEANUP | FF_CODEC_CAP_NOT_INIT_THREADSAFE, +}; -- 2.34.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".