Message ID | 20190709183433.22704-1-derek.buitenhuis@gmail.com |
---|---|
State | Superseded |
Headers | show |
On 09/07/2019 19:34, Derek Buitenhuis wrote:
> * Added two pass support.
As a side note, I really dislike the stats_out/stats_in API, since it requires
the whole stats be held in memory. This my become problematic when rav1e adds
temporal RDO (think mbtree) and the stats files grow a lot.
- Derek
On 09/07/2019 19:34, Derek Buitenhuis wrote: > +Go to @url{https://github.com/lu-zero/crav1e/} and follow the instructions to build > +the C library. To enable using rav1e in FFmpeg, pass @code{--enable-librav1e} > +to @file{./configure}. Eugh. This is supposed to point to https://github.com/xiph/rav1e/. Woops. Amended locally. - Derek
Hi, On Tue, Jul 09, 2019 at 19:34:33 +0100, Derek Buitenhuis wrote: > Port to the new send/receive API by: James Almer <jamrial@gmail.com>. Two nits: > +Sets the minimum quantizer (ceiling) to use when in bitrate mdoe. Typo -> "mode" > + av_log(avctx, AV_LOG_ERROR, "Unknown return code from rav1e_send_frame.\n"); > + return AVERROR_UNKNOWN; Feel free to include the error code in the message, as you did in librav1e_receive_packet(). Moritz
On 7/9/2019 3:34 PM, Derek Buitenhuis wrote: > Port to the new send/receive API by: James Almer <jamrial@gmail.com>. > > Signed-off-by: Derek Buitenhuis <derek.buitenhuis@gmail.com> > --- > Lots of stuff happened since v3! > > * The C API / library is now in rav1e's main repo, and officially supported. > * rav1e will bump the soname and pkg-config version on any breaking changes. > * C API is now as fast as the Rust API. > * Added two pass support. > * Added min quantizer support. > * Added tiles / speed to AVOptions. > * Mapped min/max keyint. > * Applied all the fixes requested in the last round. > --- > configure | 4 + > doc/encoders.texi | 37 +++ > doc/general.texi | 7 + > libavcodec/Makefile | 1 + > libavcodec/allcodecs.c | 1 + > libavcodec/librav1e.c | 578 +++++++++++++++++++++++++++++++++++++++++ > 6 files changed, 628 insertions(+) > create mode 100644 libavcodec/librav1e.c > > diff --git a/configure b/configure > index 4005987409..7e93812824 100755 > --- a/configure > +++ b/configure > @@ -254,6 +254,7 @@ External library support: > --enable-libopenmpt enable decoding tracked files via libopenmpt [no] > --enable-libopus enable Opus de/encoding via libopus [no] > --enable-libpulse enable Pulseaudio input via libpulse [no] > + --enable-librav1e enable AV1 encoding via rav1e [no] > --enable-librsvg enable SVG rasterization via librsvg [no] > --enable-librubberband enable rubberband needed for rubberband filter [no] > --enable-librtmp enable RTMP[E] support via librtmp [no] > @@ -1778,6 +1779,7 @@ EXTERNAL_LIBRARY_LIST=" > libopenmpt > libopus > libpulse > + librav1e > librsvg > librtmp > libshine > @@ -3174,6 +3176,7 @@ libopenmpt_demuxer_deps="libopenmpt" > libopus_decoder_deps="libopus" > libopus_encoder_deps="libopus" > libopus_encoder_select="audio_frame_queue" > +librav1e_encoder_deps="librav1e" Needs to enable extract_extradata_bsf with a _select line as well. > librsvg_decoder_deps="librsvg" > libshine_encoder_deps="libshine" > libshine_encoder_select="audio_frame_queue" > @@ -6215,6 +6218,7 @@ enabled libopus && { > } > } > enabled libpulse && require_pkg_config libpulse libpulse pulse/pulseaudio.h pa_context_new > +enabled librav1e && require_pkg_config librav1e "rav1e >= 0.1.0" rav1e.h rav1e_context_new > enabled librsvg && require_pkg_config librsvg librsvg-2.0 librsvg-2.0/librsvg/rsvg.h rsvg_handle_render_cairo > enabled librtmp && require_pkg_config librtmp librtmp librtmp/rtmp.h RTMP_Socket > enabled librubberband && require_pkg_config librubberband "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new -lstdc++ && append librubberband_extralibs "-lstdc++" > diff --git a/doc/encoders.texi b/doc/encoders.texi > index eefd124751..f316725409 100644 > --- a/doc/encoders.texi > +++ b/doc/encoders.texi > @@ -1378,6 +1378,43 @@ makes it possible to store non-rgb pix_fmts. > > @end table > > +@section librav1e > + > +rav1e AV1 encoder wrapper. > + > +Requires the presence of the rav1e headers and library during configuration. > +You need to explicitly configure the build with @code{--enable-librav1e}. > + > +@subsection Options > + > +@table @option > +@item qmax > +Sets the maximum quantizer (floor) to use when using bitrate mode. > + > +@item qmin > +Sets the minimum quantizer (ceiling) to use when in bitrate mdoe. > + > +@item qp > +Uses quantizer mode to encode at the given quantizer. > + > +@item speed > +Selects the speed preset (0-9) to encode with. > + > +@item tiles > +Selects how many tiles to encode with. > + > +@item rav1e-params > +Set rav1e options using a list of @var{key}=@var{value} pairs separated > +by ":". See @command{rav1e --help} for a list of options. > + > +For example to specify librav1e encoding options with @option{-rav1e-params}: > + > +@example > +ffmpeg -i input -c:v librav1e -b:v 500K -rav1e-params speed=5:low_latency=true output.mp4 > +@end example > + > +@end table > + > @section libaom-av1 > > libaom AV1 encoder wrapper. > diff --git a/doc/general.texi b/doc/general.texi > index 3c0c803449..72a6d73f33 100644 > --- a/doc/general.texi > +++ b/doc/general.texi > @@ -243,6 +243,13 @@ FFmpeg can use the OpenJPEG libraries for decoding/encoding J2K videos. Go to > instructions. To enable using OpenJPEG in FFmpeg, pass @code{--enable-libopenjpeg} to > @file{./configure}. > > +@section rav1e > + > +FFmpeg can make use of rav1e (Rust AV1 Encoder) via its C bindings to encode videos. > +Go to @url{https://github.com/lu-zero/crav1e/} and follow the instructions to build > +the C library. To enable using rav1e in FFmpeg, pass @code{--enable-librav1e} > +to @file{./configure}. > + > @section TwoLAME > > FFmpeg can make use of the TwoLAME library for MP2 encoding. > diff --git a/libavcodec/Makefile b/libavcodec/Makefile > index 3cd73fbcc6..cebc9d2ebc 100644 > --- a/libavcodec/Makefile > +++ b/libavcodec/Makefile > @@ -988,6 +988,7 @@ OBJS-$(CONFIG_LIBOPUS_DECODER) += libopusdec.o libopus.o \ > vorbis_data.o > OBJS-$(CONFIG_LIBOPUS_ENCODER) += libopusenc.o libopus.o \ > vorbis_data.o > +OBJS-$(CONFIG_LIBRAV1E_ENCODER) += librav1e.o > OBJS-$(CONFIG_LIBSHINE_ENCODER) += libshine.o > OBJS-$(CONFIG_LIBSPEEX_DECODER) += libspeexdec.o > OBJS-$(CONFIG_LIBSPEEX_ENCODER) += libspeexenc.o > diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c > index d2f9a39ce5..c1cc29e9d4 100644 > --- a/libavcodec/allcodecs.c > +++ b/libavcodec/allcodecs.c > @@ -703,6 +703,7 @@ extern AVCodec ff_libopenjpeg_encoder; > extern AVCodec ff_libopenjpeg_decoder; > extern AVCodec ff_libopus_encoder; > extern AVCodec ff_libopus_decoder; > +extern AVCodec ff_librav1e_encoder; > extern AVCodec ff_librsvg_decoder; > extern AVCodec ff_libshine_encoder; > extern AVCodec ff_libspeex_encoder; > diff --git a/libavcodec/librav1e.c b/libavcodec/librav1e.c > new file mode 100644 > index 0000000000..fd69ef84c1 > --- /dev/null > +++ b/libavcodec/librav1e.c > @@ -0,0 +1,578 @@ > +/* > + * librav1e encoder > + * > + * Copyright (c) 2019 Derek Buitenhuis > + * > + * 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 <rav1e.h> > + > +#include "libavutil/internal.h" > +#include "libavutil/avassert.h" > +#include "libavutil/base64.h" > +#include "libavutil/common.h" > +#include "libavutil/opt.h" > +#include "libavutil/pixdesc.h" > +#include "avcodec.h" > +#include "internal.h" > + > +typedef struct librav1eContext { > + const AVClass *class; > + > + RaContext *ctx; > + AVBSFContext *bsf; > + > + uint8_t *pass_data; > + size_t pass_pos; > + int pass_size; > + > + char *rav1e_opts; > + int quantizer; > + int speed; > + int tiles; > +} librav1eContext; > + > +static inline RaPixelRange range_map(enum AVPixelFormat pix_fmt, enum AVColorRange range) > +{ > + switch (pix_fmt) { > + case AV_PIX_FMT_YUVJ420P: > + case AV_PIX_FMT_YUVJ422P: > + case AV_PIX_FMT_YUVJ444P: > + return RA_PIXEL_RANGE_FULL; > + } > + > + switch (range) { > + case AVCOL_RANGE_MPEG: > + return RA_PIXEL_RANGE_LIMITED; > + case AVCOL_RANGE_JPEG: > + return RA_PIXEL_RANGE_FULL; > + default: > + return RA_PIXEL_RANGE_UNSPECIFIED; > + } > +} > + > +static inline RaChromaSampling pix_fmt_map(enum AVPixelFormat pix_fmt) > +{ > + switch (pix_fmt) { > + case AV_PIX_FMT_YUV420P: > + case AV_PIX_FMT_YUVJ420P: > + case AV_PIX_FMT_YUV420P10: > + case AV_PIX_FMT_YUV420P12: > + return RA_CHROMA_SAMPLING_CS420; > + case AV_PIX_FMT_YUV422P: > + case AV_PIX_FMT_YUVJ422P: > + case AV_PIX_FMT_YUV422P10: > + case AV_PIX_FMT_YUV422P12: > + return RA_CHROMA_SAMPLING_CS422; > + case AV_PIX_FMT_YUV444P: > + case AV_PIX_FMT_YUVJ444P: > + case AV_PIX_FMT_YUV444P10: > + case AV_PIX_FMT_YUV444P12: > + return RA_CHROMA_SAMPLING_CS444; > + default: > + av_assert0(0); > + } > +} > + > +static inline RaChromaSamplePosition chroma_loc_map(enum AVChromaLocation chroma_loc) > +{ > + switch (chroma_loc) { > + case AVCHROMA_LOC_LEFT: > + return RA_CHROMA_SAMPLE_POSITION_VERTICAL; > + case AVCHROMA_LOC_TOPLEFT: > + return RA_CHROMA_SAMPLE_POSITION_COLOCATED; > + default: > + return RA_CHROMA_SAMPLE_POSITION_UNKNOWN; > + } > +} > + > +static int get_stats(AVCodecContext *avctx, int eos) > +{ > + librav1eContext *ctx = avctx->priv_data; > + uint8_t *buf; > + size_t buf_size = 0; > + > + buf = rav1e_twopass_out(ctx->ctx, &buf_size); > + if (!buf) > + return 0; > + > + if (!eos) { > + uint8_t *tmp = av_fast_realloc(ctx->pass_data, &ctx->pass_size, > + ctx->pass_pos + buf_size); > + if (!tmp) { > + rav1e_twopass_unref(buf); > + return AVERROR(ENOMEM); > + } > + > + ctx->pass_data = tmp; > + memcpy(ctx->pass_data + ctx->pass_pos, buf, buf_size); > + ctx->pass_pos += buf_size; > + } else { > + size_t b64_size = AV_BASE64_SIZE(ctx->pass_pos); > + > + memcpy(ctx->pass_data, buf, buf_size); > + > + avctx->stats_out = av_malloc(b64_size); > + if (!avctx->stats_out) { > + rav1e_twopass_unref(buf); > + return AVERROR(ENOMEM); > + } > + > + av_base64_encode(avctx->stats_out, b64_size, ctx->pass_data, ctx->pass_pos); > + > + av_freep(&ctx->pass_data); > + } > + > + rav1e_twopass_unref(buf); > + > + return 0; > +} > + > +static int set_stats(AVCodecContext *avctx) > +{ > + librav1eContext *ctx = avctx->priv_data; > + int ret = 1; > + > + while (ret > 0 && ctx->pass_size - ctx->pass_pos > 0) { > + ret = rav1e_twopass_in(ctx->ctx, ctx->pass_data + ctx->pass_pos, ctx->pass_size); > + if (ret < 0) > + return AVERROR_EXTERNAL; > + ctx->pass_pos += ret; > + } > + > + return 0; > +} > + > +static av_cold int librav1e_encode_close(AVCodecContext *avctx) > +{ > + librav1eContext *ctx = avctx->priv_data; > + > + if (ctx->ctx) { > + rav1e_context_unref(ctx->ctx); > + ctx->ctx = NULL; > + } > + > + av_bsf_free(&ctx->bsf); > + av_freep(&ctx->pass_data); > + > + return 0; > +} > + > +static av_cold int librav1e_encode_init(AVCodecContext *avctx) > +{ > + librav1eContext *ctx = avctx->priv_data; > + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(avctx->pix_fmt); > + RaConfig *cfg = NULL; > + int rret; > + int ret = 0; > + > + cfg = rav1e_config_default(); > + if (!cfg) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate rav1e config.\n"); > + return AVERROR_EXTERNAL; > + } > + > + rav1e_config_set_time_base(cfg, (RaRational) { > + avctx->time_base.num * avctx->ticks_per_frame, > + avctx->time_base.den > + }); > + > + if (avctx->flags & AV_CODEC_FLAG_PASS2) { > + if (!avctx->stats_in) { > + av_log(avctx, AV_LOG_ERROR, "No stats file provided for second pass.\n"); > + ret = AVERROR(EINVAL); > + goto end; > + } > + > + ctx->pass_size = (strlen(avctx->stats_in) * 3) / 4; > + ctx->pass_data = av_malloc(ctx->pass_size); > + if (!ctx->pass_data) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate stats buffer.\n"); > + ret = AVERROR(ENOMEM); > + goto end; > + } > + > + ctx->pass_size = av_base64_decode(ctx->pass_data, avctx->stats_in, ctx->pass_size); > + if (ctx->pass_size < 0) { > + av_log(avctx, AV_LOG_ERROR, "Invalid pass file.\n"); > + ret = AVERROR(EINVAL); > + goto end; > + } > + } > + > + if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { > + const AVBitStreamFilter *filter = av_bsf_get_by_name("extract_extradata"); > + int bret; > + > + if (!filter) { > + av_log(avctx, AV_LOG_ERROR, "extract_extradata bitstream filter " > + "not found. This is a bug, please report it.\n"); > + ret = AVERROR_BUG; > + goto end; > + } > + > + bret = av_bsf_alloc(filter, &ctx->bsf); > + if (bret < 0) { > + ret = bret; > + goto end; > + } > + > + bret = avcodec_parameters_from_context(ctx->bsf->par_in, avctx); > + if (bret < 0) { > + ret = bret; > + goto end; > + } > + > + bret = av_bsf_init(ctx->bsf); > + if (bret < 0) { > + ret = bret; > + goto end; > + } > + } > + > + if (ctx->rav1e_opts) { > + AVDictionary *dict = NULL; > + AVDictionaryEntry *en = NULL; > + > + if (!av_dict_parse_string(&dict, ctx->rav1e_opts, "=", ":", 0)) { > + while (en = av_dict_get(dict, "", en, AV_DICT_IGNORE_SUFFIX)) { > + int parse_ret = rav1e_config_parse(cfg, en->key, en->value); > + if (parse_ret < 0) > + av_log(avctx, AV_LOG_WARNING, "Invalid value for %s: %s.\n", en->key, en->value); > + } > + av_dict_free(&dict); > + } > + } > + > + rret = rav1e_config_parse_int(cfg, "width", avctx->width); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Invalid width passed to rav1e.\n"); > + ret = AVERROR_INVALIDDATA; > + goto end; > + } > + > + rret = rav1e_config_parse_int(cfg, "height", avctx->height); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Invalid width passed to rav1e.\n"); > + ret = AVERROR_INVALIDDATA; > + goto end; > + } > + > + rret = rav1e_config_parse_int(cfg, "threads", avctx->thread_count); > + if (rret < 0) > + av_log(avctx, AV_LOG_WARNING, "Invalid number of threads, defaulting to auto.\n"); > + > + if (ctx->speed >= 0) { > + rret = rav1e_config_parse_int(cfg, "speed", ctx->speed); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not set speed preset.\n"); > + ret = AVERROR_EXTERNAL; > + goto end; > + } > + } > + > + if (ctx->tiles >= 0) { > + rret = rav1e_config_parse_int(cfg, "tiles", ctx->speed); ctx->tiles Also, it may be a good idea to instead look into making the "tiles" option parsing code in libaomenc shared and reuse it here, so it's consistent between encoders. With it you can pass a Cols x Rows string using the AV_OPT_TYPE_IMAGE_SIZE AVOption type and derive values you can then map to rav1e's tile_rows_log2 and tile_cols_log2 options. In the meantime, you can define tile-columns and tile-rows AVOptions and directly map them to the aforementioned rav1e options. > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not set number of tiles to encode with.\n"); > + ret = AVERROR_EXTERNAL; > + goto end; > + } > + } > + > + if (avctx->gop_size > 0) { > + rret = rav1e_config_parse_int(cfg, "key_frame_interval", avctx->gop_size); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not set max keyint.\n"); > + ret = AVERROR_EXTERNAL; > + goto end; > + } > + } > + > + if (avctx->keyint_min > 0) { > + rret = rav1e_config_parse_int(cfg, "min_key_frame_interval", avctx->keyint_min); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not set min keyint.\n"); > + ret = AVERROR_EXTERNAL; > + goto end; > + } > + } > + > + if (avctx->bit_rate && ctx->quantizer < 0) { > + int max_quantizer = avctx->qmax >= 0 ? avctx->qmax : 255; > + > + rret = rav1e_config_parse_int(cfg, "quantizer", max_quantizer); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not set max quantizer.\n"); > + ret = AVERROR_EXTERNAL; > + goto end; > + } > + > + if (avctx->qmin >= 0) { > + rret = rav1e_config_parse_int(cfg, "min_quantizer", avctx->qmin); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not set min quantizer.\n"); > + ret = AVERROR_EXTERNAL; > + goto end; > + } > + } > + > + rret = rav1e_config_parse_int(cfg, "bitrate", avctx->bit_rate); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not set bitrate.\n"); > + ret = AVERROR_INVALIDDATA; > + goto end; > + } > + } else if (ctx->quantizer >= 0) { > + rret = rav1e_config_parse_int(cfg, "quantizer", ctx->quantizer); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not set quantizer.\n"); > + ret = AVERROR_EXTERNAL; > + goto end; > + } > + } > + > + rret = rav1e_config_set_pixel_format(cfg, desc->comp[0].depth, > + pix_fmt_map(avctx->pix_fmt), > + chroma_loc_map(avctx->chroma_sample_location), > + range_map(avctx->pix_fmt, avctx->color_range)); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Failed to set pixel format properties.\n"); > + ret = AVERROR_INVALIDDATA; > + goto end; > + } > + > + /* rav1e's colorspace enums match standard values. */ > + rret = rav1e_config_set_color_description(cfg, (RaMatrixCoefficients) avctx->colorspace, > + (RaColorPrimaries) avctx->color_primaries, > + (RaTransferCharacteristics) avctx->color_trc); > + if (rret < 0) { > + av_log(avctx, AV_LOG_WARNING, "Failed to set color properties.\n"); > + if (avctx->err_recognition & AV_EF_EXPLODE) { > + ret = AVERROR_INVALIDDATA; > + goto end; > + } > + } > + > + ctx->ctx = rav1e_context_new(cfg); > + if (!ctx->ctx) { > + av_log(avctx, AV_LOG_ERROR, "Failed to create rav1e encode context.\n"); > + ret = AVERROR_EXTERNAL; > + goto end; > + } > + > + ret = 0; > + > +end: > + > + rav1e_config_unref(cfg); > + > + return ret; > +} > + > +static int librav1e_send_frame(AVCodecContext *avctx, const AVFrame *frame) > +{ > + librav1eContext *ctx = avctx->priv_data; > + RaFrame *rframe = NULL; > + int ret; > + > + if (frame) { > + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); > + > + rframe = rav1e_frame_new(ctx->ctx); > + if (!rframe) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate new rav1e frame.\n"); > + return AVERROR(ENOMEM); > + } > + > + for (int i = 0; i < desc->nb_components; i++) { > + int shift = i ? desc->log2_chroma_h : 0; > + int bytes = desc->comp[0].depth == 8 ? 1 : 2; > + rav1e_frame_fill_plane(rframe, i, frame->data[i], > + (frame->height >> shift) * frame->linesize[i], > + frame->linesize[i], bytes); > + } > + } > + > + ret = rav1e_send_frame(ctx->ctx, rframe); > + if (rframe) > + rav1e_frame_unref(rframe); /* No need to unref if flushing. */ > + > + switch(ret) { > + case RA_ENCODER_STATUS_SUCCESS: > + break; > + case RA_ENCODER_STATUS_ENOUGH_DATA: > + return AVERROR(EAGAIN); > + case RA_ENCODER_STATUS_FAILURE: > + av_log(avctx, AV_LOG_ERROR, "Could not send frame.\n"); > + return AVERROR_EXTERNAL; > + default: > + av_log(avctx, AV_LOG_ERROR, "Unknown return code from rav1e_send_frame.\n"); > + return AVERROR_UNKNOWN; > + } > + > + return 0; > +} > + > +static void librav1e_packet_unref(void *opaque, uint8_t *data) > +{ > + RaPacket *rpkt = opaque; > + > + rav1e_packet_unref(rpkt); > +} > + > +static int librav1e_receive_packet(AVCodecContext *avctx, AVPacket *pkt) > +{ > + librav1eContext *ctx = avctx->priv_data; > + RaPacket *rpkt = NULL; > + int ret; > + > +retry: > + > + if (avctx->flags & AV_CODEC_FLAG_PASS1) { > + int sret = get_stats(avctx, 0); > + if (sret < 0) > + return sret; > + } else if (avctx->flags & AV_CODEC_FLAG_PASS2) { > + int sret = set_stats(avctx); > + if (sret < 0) > + return sret; > + } > + > + ret = rav1e_receive_packet(ctx->ctx, &rpkt); > + switch(ret) { > + case RA_ENCODER_STATUS_SUCCESS: > + break; > + case RA_ENCODER_STATUS_LIMIT_REACHED: > + if (avctx->flags & AV_CODEC_FLAG_PASS1) { > + int sret = get_stats(avctx, 1); > + if (sret < 0) > + return sret; > + } > + return AVERROR_EOF; > + case RA_ENCODER_STATUS_ENCODED: > + if (avctx->internal->draining) > + goto retry; > + return AVERROR(EAGAIN); Is rav1e_send_frame() guaranteed to not return RA_ENCODER_STATUS_ENOUGH_DATA if called right after rav1e_receive_packet() returned RA_ENCODER_STATUS_ENCODED? In other words, if it encoded a frame but didn't return it, is it safe to assume its buffers are not full and will not reject new data passed to it? If not, then we would have to do goto retry here unconditionally, draining or not. The doxy for avcodec_receive_packet and avcodec_send_frame state that when one returns EAGAIN, the other is guaranteed to return something else. > + case RA_ENCODER_STATUS_NEED_MORE_DATA: > + if (avctx->internal->draining) { > + av_log(avctx, AV_LOG_ERROR, "Unexpected error when receiving packet after EOF.\n"); > + return AVERROR_EXTERNAL; > + } > + return AVERROR(EAGAIN); > + case RA_ENCODER_STATUS_FAILURE: > + av_log(avctx, AV_LOG_ERROR, "Could not encode frame.\n"); > + return AVERROR_EXTERNAL; > + default: > + av_log(avctx, AV_LOG_ERROR, "Unknown return code %d from rav1e_receive_packet.\n", ret); > + return AVERROR_UNKNOWN; > + } > + > + pkt->buf = av_buffer_create((uint8_t *) rpkt->data, rpkt->len, librav1e_packet_unref, > + rpkt, AV_BUFFER_FLAG_READONLY); > + if (!pkt->buf) { > + rav1e_packet_unref(rpkt); > + return AVERROR(ENOMEM); > + } > + > + pkt->data = (uint8_t *) rpkt->data; > + pkt->size = rpkt->len; > + > + if (rpkt->frame_type == RA_FRAME_TYPE_KEY) > + pkt->flags |= AV_PKT_FLAG_KEY; > + > + pkt->pts = pkt->dts = rpkt->input_frameno * avctx->ticks_per_frame; > + > + if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { > + int ret = av_bsf_send_packet(ctx->bsf, pkt); > + if (ret < 0) { > + av_log(avctx, AV_LOG_ERROR, "extradata extraction send failed.\n"); > + av_packet_unref(pkt); > + return ret; > + } > + > + ret = av_bsf_receive_packet(ctx->bsf, pkt); > + if (ret < 0) { > + av_log(avctx, AV_LOG_ERROR, "extradata extraction receive failed.\n"); > + av_packet_unref(pkt); > + return ret; > + } > + } > + > + return 0; > +} > + > +#define OFFSET(x) offsetof(librav1eContext, x) > +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM > + > +static const AVOption options[] = { > + { "qp", "use constant quantizer mode", OFFSET(quantizer), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 255, VE }, > + { "speed", "what speed preset to use", OFFSET(speed), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 9, VE }, > + { "tiles", "number of tiles encode with", OFFSET(tiles), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT64_MAX, VE }, > + { "rav1e-params", "set the rav1e configuration using a :-separated list of key=value parameters", OFFSET(rav1e_opts), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE }, > + { NULL } > +}; > + > +static const AVCodecDefault librav1e_defaults[] = { > + { "g", "0" }, > + { "keyint_min", "0" }, > + { "qmax", "-1" }, > + { "qmin", "-1" }, > + { NULL } > +}; > + > +const enum AVPixelFormat librav1e_pix_fmts[] = { > + AV_PIX_FMT_YUV420P, > + AV_PIX_FMT_YUVJ420P, > + AV_PIX_FMT_YUV420P10, > + AV_PIX_FMT_YUV420P12, > + AV_PIX_FMT_YUV422P, > + AV_PIX_FMT_YUVJ422P, > + AV_PIX_FMT_YUV422P10, > + AV_PIX_FMT_YUV422P12, > + AV_PIX_FMT_YUV444P, > + AV_PIX_FMT_YUVJ444P, > + AV_PIX_FMT_YUV444P10, > + AV_PIX_FMT_YUV444P12, > + AV_PIX_FMT_NONE > +}; > + > +static const AVClass class = { > + .class_name = "librav1e", > + .item_name = av_default_item_name, > + .option = options, > + .version = LIBAVUTIL_VERSION_INT, > +}; > + > +AVCodec ff_librav1e_encoder = { > + .name = "librav1e", > + .long_name = NULL_IF_CONFIG_SMALL("librav1e AV1"), > + .type = AVMEDIA_TYPE_VIDEO, > + .id = AV_CODEC_ID_AV1, > + .init = librav1e_encode_init, > + .send_frame = librav1e_send_frame, > + .receive_packet = librav1e_receive_packet, > + .close = librav1e_encode_close, > + .priv_data_size = sizeof(librav1eContext), > + .priv_class = &class, > + .defaults = librav1e_defaults, > + .pix_fmts = librav1e_pix_fmts, > + .capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AUTO_THREADS, > + .caps_internal = FF_CODEC_CAP_INIT_CLEANUP, > + .wrapper_name = "librav1e", > +}; >
On 09/07/2019 22:06, James Almer wrote: >> @@ -3174,6 +3176,7 @@ libopenmpt_demuxer_deps="libopenmpt" >> libopus_decoder_deps="libopus" >> libopus_encoder_deps="libopus" >> libopus_encoder_select="audio_frame_queue" >> +librav1e_encoder_deps="librav1e" > > Needs to enable extract_extradata_bsf with a _select line as well. Fixed locally. >> + if (ctx->tiles >= 0) { >> + rret = rav1e_config_parse_int(cfg, "tiles", ctx->speed); > > ctx->tiles Fixed locally. > > Also, it may be a good idea to instead look into making the "tiles" > option parsing code in libaomenc shared and reuse it here, so it's > consistent between encoders. With it you can pass a Cols x Rows string > using the AV_OPT_TYPE_IMAGE_SIZE AVOption type and derive values you can > then map to rav1e's tile_rows_log2 and tile_cols_log2 options. Heavily disagree. rav1e has its own 'tiles' option that determines the right cols/rows to use for a reason, and I will not be emulating libaomenc's algo instead of using the one provided by rav1e. It's confusing at best (since it goes against the rav1e API, and makes the official CLI and ffmpeg work differently). > In the meantime, you can define tile-columns and tile-rows AVOptions and > directly map them to the aforementioned rav1e options. Why? What's the use-case? "Being the same as libaomenc" is not a good use case. >> + case RA_ENCODER_STATUS_ENCODED: >> + if (avctx->internal->draining) >> + goto retry; >> + return AVERROR(EAGAIN); > > Is rav1e_send_frame() guaranteed to not return > RA_ENCODER_STATUS_ENOUGH_DATA if called right after > rav1e_receive_packet() returned RA_ENCODER_STATUS_ENCODED? > In other words, if it encoded a frame but didn't return it, is it safe > to assume its buffers are not full and will not reject new data passed > to it? rav1e, in fact, never returns this error. I need to check what the guarantee is, though - I'm not sure what the future plans are. > If not, then we would have to do goto retry here unconditionally, > draining or not. The doxy for avcodec_receive_packet and > avcodec_send_frame state that when one returns EAGAIN, the other is > guaranteed to return something else. [...] - Derek
On 09/07/2019 21:23, Moritz Barsnick wrote: >> +Sets the minimum quantizer (ceiling) to use when in bitrate mdoe. > > Typo -> "mode" Fixed locally. > >> + av_log(avctx, AV_LOG_ERROR, "Unknown return code from rav1e_send_frame.\n"); >> + return AVERROR_UNKNOWN; > > Feel free to include the error code in the message, as you did in > librav1e_receive_packet(). OK. - Derek
On 7/10/2019 9:22 AM, Derek Buitenhuis wrote: > On 09/07/2019 22:06, James Almer wrote: >>> @@ -3174,6 +3176,7 @@ libopenmpt_demuxer_deps="libopenmpt" >>> libopus_decoder_deps="libopus" >>> libopus_encoder_deps="libopus" >>> libopus_encoder_select="audio_frame_queue" >>> +librav1e_encoder_deps="librav1e" >> >> Needs to enable extract_extradata_bsf with a _select line as well. > > Fixed locally. > >>> + if (ctx->tiles >= 0) { >>> + rret = rav1e_config_parse_int(cfg, "tiles", ctx->speed); >> >> ctx->tiles > > Fixed locally. > >> >> Also, it may be a good idea to instead look into making the "tiles" >> option parsing code in libaomenc shared and reuse it here, so it's >> consistent between encoders. With it you can pass a Cols x Rows string >> using the AV_OPT_TYPE_IMAGE_SIZE AVOption type and derive values you can >> then map to rav1e's tile_rows_log2 and tile_cols_log2 options. > > Heavily disagree. rav1e has its own 'tiles' option that determines the right > cols/rows to use for a reason, and I will not be emulating libaomenc's algo > instead of using the one provided by rav1e. It's confusing at best (since > it goes against the rav1e API, and makes the official CLI and ffmpeg work > differently). It's not libaom code, it's Mark Thompson's code. It derives tile_rows_log2 and tile_cols_log2 values, which are what libaom expects, from a Cols X Rows string (image size AVOption type), so you can tell the encoder if you want a sample with exactly 4x2 tiles or such. But if you prefer the tiles option to match the behavior or rav1e's CLI, then I'm fine with it. > >> In the meantime, you can define tile-columns and tile-rows AVOptions and >> directly map them to the aforementioned rav1e options. > > Why? What's the use-case? "Being the same as libaomenc" is not a good use case. Command line option consistency between encoders is a good reason. It's why you're using "qp" instead of "quantizer" or whatever, after all. Rav1e lets you set cols and rows in log2 values much like libaom, right? Then why not add options that map directly to them? Even if you don't use Mark's code to convert Cols x Rows strings, both a tiles and tile-cols/rows options can coexist, with the latter two taking precedence over the former, since they give the user more control over tiling. > >>> + case RA_ENCODER_STATUS_ENCODED: >>> + if (avctx->internal->draining) >>> + goto retry; >>> + return AVERROR(EAGAIN); >> >> Is rav1e_send_frame() guaranteed to not return >> RA_ENCODER_STATUS_ENOUGH_DATA if called right after >> rav1e_receive_packet() returned RA_ENCODER_STATUS_ENCODED? >> In other words, if it encoded a frame but didn't return it, is it safe >> to assume its buffers are not full and will not reject new data passed >> to it? > > rav1e, in fact, never returns this error. I need to check what the > guarantee is, though - I'm not sure what the future plans are. Ok, should be good then. > >> If not, then we would have to do goto retry here unconditionally, >> draining or not. The doxy for avcodec_receive_packet and >> avcodec_send_frame state that when one returns EAGAIN, the other is >> guaranteed to return something else. > > [...] > > - Derek > _______________________________________________ > 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". >
On 10/07/2019 13:56, James Almer wrote: >> Heavily disagree. rav1e has its own 'tiles' option that determines the right >> cols/rows to use for a reason, and I will not be emulating libaomenc's algo >> instead of using the one provided by rav1e. It's confusing at best (since >> it goes against the rav1e API, and makes the official CLI and ffmpeg work >> differently). > > It's not libaom code, it's Mark Thompson's code. It derives > tile_rows_log2 and tile_cols_log2 values, which are what libaom expects, > from a Cols X Rows string (image size AVOption type), so you can tell > the encoder if you want a sample with exactly 4x2 tiles or such. That should be handled by the tile-columns and tile-rows options below, I think. > > But if you prefer the tiles option to match the behavior or rav1e's CLI, > then I'm fine with it. I do prefer this. >> Why? What's the use-case? "Being the same as libaomenc" is not a good use case. > > Command line option consistency between encoders is a good reason. It's > why you're using "qp" instead of "quantizer" or whatever, after all. > Rav1e lets you set cols and rows in log2 values much like libaom, right? > Then why not add options that map directly to them? Even if you don't > use Mark's code to convert Cols x Rows strings, both a tiles and > tile-cols/rows options can coexist, with the latter two taking > precedence over the former, since they give the user more control over > tiling. That's fair enough, having users set log2 values is crappy. I'll add these. - Derek
On 7/9/2019 3:34 PM, Derek Buitenhuis wrote: > Port to the new send/receive API by: James Almer <jamrial@gmail.com>. > > Signed-off-by: Derek Buitenhuis <derek.buitenhuis@gmail.com> > --- > Lots of stuff happened since v3! > > * The C API / library is now in rav1e's main repo, and officially supported. > * rav1e will bump the soname and pkg-config version on any breaking changes. > * C API is now as fast as the Rust API. > * Added two pass support. > * Added min quantizer support. > * Added tiles / speed to AVOptions. > * Mapped min/max keyint. > * Applied all the fixes requested in the last round. > --- > configure | 4 + > doc/encoders.texi | 37 +++ > doc/general.texi | 7 + > libavcodec/Makefile | 1 + > libavcodec/allcodecs.c | 1 + > libavcodec/librav1e.c | 578 +++++++++++++++++++++++++++++++++++++++++ > 6 files changed, 628 insertions(+) > create mode 100644 libavcodec/librav1e.c > +static int get_stats(AVCodecContext *avctx, int eos) > +{ > + librav1eContext *ctx = avctx->priv_data; > + uint8_t *buf; > + size_t buf_size = 0; > + > + buf = rav1e_twopass_out(ctx->ctx, &buf_size); > + if (!buf) > + return 0; > + > + if (!eos) { > + uint8_t *tmp = av_fast_realloc(ctx->pass_data, &ctx->pass_size, > + ctx->pass_pos + buf_size); Just use av_reallocp(). Each call will make the buffer bigger, so you're not really making use the no-op benefits from av_fast_realloc(), which only trigger if newsize <= size. > +static av_cold int librav1e_encode_init(AVCodecContext *avctx) > +{ > + librav1eContext *ctx = avctx->priv_data; > + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(avctx->pix_fmt); > + RaConfig *cfg = NULL; > + int rret; > + int ret = 0; > + > + cfg = rav1e_config_default(); > + if (!cfg) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate rav1e config.\n"); > + return AVERROR_EXTERNAL; > + } > + > + rav1e_config_set_time_base(cfg, (RaRational) { > + avctx->time_base.num * avctx->ticks_per_frame, > + avctx->time_base.den > + }); > + > + if (avctx->flags & AV_CODEC_FLAG_PASS2) { > + if (!avctx->stats_in) { > + av_log(avctx, AV_LOG_ERROR, "No stats file provided for second pass.\n"); > + ret = AVERROR(EINVAL); > + goto end; > + } > + > + ctx->pass_size = (strlen(avctx->stats_in) * 3) / 4; AV_BASE64_DECODE_SIZE(strlen(avctx->stats_in));
James Almer (12019-07-13): > > + uint8_t *tmp = av_fast_realloc(ctx->pass_data, &ctx->pass_size, > > + ctx->pass_pos + buf_size); > Just use av_reallocp(). Each call will make the buffer bigger, so you're > not really making use the no-op benefits from av_fast_realloc(), which > only trigger if newsize <= size. av_fast_realloc() also allocates 17/16 of the requested memory, which makes the incremental building of a buffer linear instead of quadratic. Regards,
On 13/07/2019 19:37, James Almer wrote: > Just use av_reallocp(). Each call will make the buffer bigger, so you're > not really making use the no-op benefits from av_fast_realloc(), which > only trigger if newsize <= size. I'll wait for your reply to Nicholas and do whichevr people agree on. >> + ctx->pass_size = (strlen(avctx->stats_in) * 3) / 4; > > AV_BASE64_DECODE_SIZE(strlen(avctx->stats_in)); I copied this directly from libtheoraenc.c which has had this for more than 10 years... is it wrong? Should it be changed too? - Derek
On 7/15/2019 11:23 AM, Derek Buitenhuis wrote: > On 13/07/2019 19:37, James Almer wrote: >> Just use av_reallocp(). Each call will make the buffer bigger, so you're >> not really making use the no-op benefits from av_fast_realloc(), which >> only trigger if newsize <= size. > > I'll wait for your reply to Nicholas and do whichevr people agree on. Leave it as av_fast_realloc(). My suggestion was only a NIT. > >>> + ctx->pass_size = (strlen(avctx->stats_in) * 3) / 4; >> >> AV_BASE64_DECODE_SIZE(strlen(avctx->stats_in)); > > I copied this directly from libtheoraenc.c which has had this for more than 10 > years... is it wrong? Should it be changed too? It's not wrong, that helper macro was added three years ago and it expands to the same code, except promoting the intermediary result to int64_t to prevent overflow. And i think it should ideally be changed in libtheora/libvpx as well, yes. > > - Derek > _______________________________________________ > 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". >
On 15/07/2019 15:53, James Almer wrote: >>> AV_BASE64_DECODE_SIZE(strlen(avctx->stats_in)); >> >> I copied this directly from libtheoraenc.c which has had this for more than 10 >> years... is it wrong? Should it be changed too? > > It's not wrong, that helper macro was added three years ago and it > expands to the same code, except promoting the intermediary result to > int64_t to prevent overflow. > And i think it should ideally be changed in libtheora/libvpx as well, yes. OK. - Derek
diff --git a/configure b/configure index 4005987409..7e93812824 100755 --- a/configure +++ b/configure @@ -254,6 +254,7 @@ External library support: --enable-libopenmpt enable decoding tracked files via libopenmpt [no] --enable-libopus enable Opus de/encoding via libopus [no] --enable-libpulse enable Pulseaudio input via libpulse [no] + --enable-librav1e enable AV1 encoding via rav1e [no] --enable-librsvg enable SVG rasterization via librsvg [no] --enable-librubberband enable rubberband needed for rubberband filter [no] --enable-librtmp enable RTMP[E] support via librtmp [no] @@ -1778,6 +1779,7 @@ EXTERNAL_LIBRARY_LIST=" libopenmpt libopus libpulse + librav1e librsvg librtmp libshine @@ -3174,6 +3176,7 @@ libopenmpt_demuxer_deps="libopenmpt" libopus_decoder_deps="libopus" libopus_encoder_deps="libopus" libopus_encoder_select="audio_frame_queue" +librav1e_encoder_deps="librav1e" librsvg_decoder_deps="librsvg" libshine_encoder_deps="libshine" libshine_encoder_select="audio_frame_queue" @@ -6215,6 +6218,7 @@ enabled libopus && { } } enabled libpulse && require_pkg_config libpulse libpulse pulse/pulseaudio.h pa_context_new +enabled librav1e && require_pkg_config librav1e "rav1e >= 0.1.0" rav1e.h rav1e_context_new enabled librsvg && require_pkg_config librsvg librsvg-2.0 librsvg-2.0/librsvg/rsvg.h rsvg_handle_render_cairo enabled librtmp && require_pkg_config librtmp librtmp librtmp/rtmp.h RTMP_Socket enabled librubberband && require_pkg_config librubberband "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new -lstdc++ && append librubberband_extralibs "-lstdc++" diff --git a/doc/encoders.texi b/doc/encoders.texi index eefd124751..f316725409 100644 --- a/doc/encoders.texi +++ b/doc/encoders.texi @@ -1378,6 +1378,43 @@ makes it possible to store non-rgb pix_fmts. @end table +@section librav1e + +rav1e AV1 encoder wrapper. + +Requires the presence of the rav1e headers and library during configuration. +You need to explicitly configure the build with @code{--enable-librav1e}. + +@subsection Options + +@table @option +@item qmax +Sets the maximum quantizer (floor) to use when using bitrate mode. + +@item qmin +Sets the minimum quantizer (ceiling) to use when in bitrate mdoe. + +@item qp +Uses quantizer mode to encode at the given quantizer. + +@item speed +Selects the speed preset (0-9) to encode with. + +@item tiles +Selects how many tiles to encode with. + +@item rav1e-params +Set rav1e options using a list of @var{key}=@var{value} pairs separated +by ":". See @command{rav1e --help} for a list of options. + +For example to specify librav1e encoding options with @option{-rav1e-params}: + +@example +ffmpeg -i input -c:v librav1e -b:v 500K -rav1e-params speed=5:low_latency=true output.mp4 +@end example + +@end table + @section libaom-av1 libaom AV1 encoder wrapper. diff --git a/doc/general.texi b/doc/general.texi index 3c0c803449..72a6d73f33 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -243,6 +243,13 @@ FFmpeg can use the OpenJPEG libraries for decoding/encoding J2K videos. Go to instructions. To enable using OpenJPEG in FFmpeg, pass @code{--enable-libopenjpeg} to @file{./configure}. +@section rav1e + +FFmpeg can make use of rav1e (Rust AV1 Encoder) via its C bindings to encode videos. +Go to @url{https://github.com/lu-zero/crav1e/} and follow the instructions to build +the C library. To enable using rav1e in FFmpeg, pass @code{--enable-librav1e} +to @file{./configure}. + @section TwoLAME FFmpeg can make use of the TwoLAME library for MP2 encoding. diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 3cd73fbcc6..cebc9d2ebc 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -988,6 +988,7 @@ OBJS-$(CONFIG_LIBOPUS_DECODER) += libopusdec.o libopus.o \ vorbis_data.o OBJS-$(CONFIG_LIBOPUS_ENCODER) += libopusenc.o libopus.o \ vorbis_data.o +OBJS-$(CONFIG_LIBRAV1E_ENCODER) += librav1e.o OBJS-$(CONFIG_LIBSHINE_ENCODER) += libshine.o OBJS-$(CONFIG_LIBSPEEX_DECODER) += libspeexdec.o OBJS-$(CONFIG_LIBSPEEX_ENCODER) += libspeexenc.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index d2f9a39ce5..c1cc29e9d4 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -703,6 +703,7 @@ extern AVCodec ff_libopenjpeg_encoder; extern AVCodec ff_libopenjpeg_decoder; extern AVCodec ff_libopus_encoder; extern AVCodec ff_libopus_decoder; +extern AVCodec ff_librav1e_encoder; extern AVCodec ff_librsvg_decoder; extern AVCodec ff_libshine_encoder; extern AVCodec ff_libspeex_encoder; diff --git a/libavcodec/librav1e.c b/libavcodec/librav1e.c new file mode 100644 index 0000000000..fd69ef84c1 --- /dev/null +++ b/libavcodec/librav1e.c @@ -0,0 +1,578 @@ +/* + * librav1e encoder + * + * Copyright (c) 2019 Derek Buitenhuis + * + * 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 <rav1e.h> + +#include "libavutil/internal.h" +#include "libavutil/avassert.h" +#include "libavutil/base64.h" +#include "libavutil/common.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avcodec.h" +#include "internal.h" + +typedef struct librav1eContext { + const AVClass *class; + + RaContext *ctx; + AVBSFContext *bsf; + + uint8_t *pass_data; + size_t pass_pos; + int pass_size; + + char *rav1e_opts; + int quantizer; + int speed; + int tiles; +} librav1eContext; + +static inline RaPixelRange range_map(enum AVPixelFormat pix_fmt, enum AVColorRange range) +{ + switch (pix_fmt) { + case AV_PIX_FMT_YUVJ420P: + case AV_PIX_FMT_YUVJ422P: + case AV_PIX_FMT_YUVJ444P: + return RA_PIXEL_RANGE_FULL; + } + + switch (range) { + case AVCOL_RANGE_MPEG: + return RA_PIXEL_RANGE_LIMITED; + case AVCOL_RANGE_JPEG: + return RA_PIXEL_RANGE_FULL; + default: + return RA_PIXEL_RANGE_UNSPECIFIED; + } +} + +static inline RaChromaSampling pix_fmt_map(enum AVPixelFormat pix_fmt) +{ + switch (pix_fmt) { + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUVJ420P: + case AV_PIX_FMT_YUV420P10: + case AV_PIX_FMT_YUV420P12: + return RA_CHROMA_SAMPLING_CS420; + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUVJ422P: + case AV_PIX_FMT_YUV422P10: + case AV_PIX_FMT_YUV422P12: + return RA_CHROMA_SAMPLING_CS422; + case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUVJ444P: + case AV_PIX_FMT_YUV444P10: + case AV_PIX_FMT_YUV444P12: + return RA_CHROMA_SAMPLING_CS444; + default: + av_assert0(0); + } +} + +static inline RaChromaSamplePosition chroma_loc_map(enum AVChromaLocation chroma_loc) +{ + switch (chroma_loc) { + case AVCHROMA_LOC_LEFT: + return RA_CHROMA_SAMPLE_POSITION_VERTICAL; + case AVCHROMA_LOC_TOPLEFT: + return RA_CHROMA_SAMPLE_POSITION_COLOCATED; + default: + return RA_CHROMA_SAMPLE_POSITION_UNKNOWN; + } +} + +static int get_stats(AVCodecContext *avctx, int eos) +{ + librav1eContext *ctx = avctx->priv_data; + uint8_t *buf; + size_t buf_size = 0; + + buf = rav1e_twopass_out(ctx->ctx, &buf_size); + if (!buf) + return 0; + + if (!eos) { + uint8_t *tmp = av_fast_realloc(ctx->pass_data, &ctx->pass_size, + ctx->pass_pos + buf_size); + if (!tmp) { + rav1e_twopass_unref(buf); + return AVERROR(ENOMEM); + } + + ctx->pass_data = tmp; + memcpy(ctx->pass_data + ctx->pass_pos, buf, buf_size); + ctx->pass_pos += buf_size; + } else { + size_t b64_size = AV_BASE64_SIZE(ctx->pass_pos); + + memcpy(ctx->pass_data, buf, buf_size); + + avctx->stats_out = av_malloc(b64_size); + if (!avctx->stats_out) { + rav1e_twopass_unref(buf); + return AVERROR(ENOMEM); + } + + av_base64_encode(avctx->stats_out, b64_size, ctx->pass_data, ctx->pass_pos); + + av_freep(&ctx->pass_data); + } + + rav1e_twopass_unref(buf); + + return 0; +} + +static int set_stats(AVCodecContext *avctx) +{ + librav1eContext *ctx = avctx->priv_data; + int ret = 1; + + while (ret > 0 && ctx->pass_size - ctx->pass_pos > 0) { + ret = rav1e_twopass_in(ctx->ctx, ctx->pass_data + ctx->pass_pos, ctx->pass_size); + if (ret < 0) + return AVERROR_EXTERNAL; + ctx->pass_pos += ret; + } + + return 0; +} + +static av_cold int librav1e_encode_close(AVCodecContext *avctx) +{ + librav1eContext *ctx = avctx->priv_data; + + if (ctx->ctx) { + rav1e_context_unref(ctx->ctx); + ctx->ctx = NULL; + } + + av_bsf_free(&ctx->bsf); + av_freep(&ctx->pass_data); + + return 0; +} + +static av_cold int librav1e_encode_init(AVCodecContext *avctx) +{ + librav1eContext *ctx = avctx->priv_data; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(avctx->pix_fmt); + RaConfig *cfg = NULL; + int rret; + int ret = 0; + + cfg = rav1e_config_default(); + if (!cfg) { + av_log(avctx, AV_LOG_ERROR, "Could not allocate rav1e config.\n"); + return AVERROR_EXTERNAL; + } + + rav1e_config_set_time_base(cfg, (RaRational) { + avctx->time_base.num * avctx->ticks_per_frame, + avctx->time_base.den + }); + + if (avctx->flags & AV_CODEC_FLAG_PASS2) { + if (!avctx->stats_in) { + av_log(avctx, AV_LOG_ERROR, "No stats file provided for second pass.\n"); + ret = AVERROR(EINVAL); + goto end; + } + + ctx->pass_size = (strlen(avctx->stats_in) * 3) / 4; + ctx->pass_data = av_malloc(ctx->pass_size); + if (!ctx->pass_data) { + av_log(avctx, AV_LOG_ERROR, "Could not allocate stats buffer.\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + ctx->pass_size = av_base64_decode(ctx->pass_data, avctx->stats_in, ctx->pass_size); + if (ctx->pass_size < 0) { + av_log(avctx, AV_LOG_ERROR, "Invalid pass file.\n"); + ret = AVERROR(EINVAL); + goto end; + } + } + + if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { + const AVBitStreamFilter *filter = av_bsf_get_by_name("extract_extradata"); + int bret; + + if (!filter) { + av_log(avctx, AV_LOG_ERROR, "extract_extradata bitstream filter " + "not found. This is a bug, please report it.\n"); + ret = AVERROR_BUG; + goto end; + } + + bret = av_bsf_alloc(filter, &ctx->bsf); + if (bret < 0) { + ret = bret; + goto end; + } + + bret = avcodec_parameters_from_context(ctx->bsf->par_in, avctx); + if (bret < 0) { + ret = bret; + goto end; + } + + bret = av_bsf_init(ctx->bsf); + if (bret < 0) { + ret = bret; + goto end; + } + } + + if (ctx->rav1e_opts) { + AVDictionary *dict = NULL; + AVDictionaryEntry *en = NULL; + + if (!av_dict_parse_string(&dict, ctx->rav1e_opts, "=", ":", 0)) { + while (en = av_dict_get(dict, "", en, AV_DICT_IGNORE_SUFFIX)) { + int parse_ret = rav1e_config_parse(cfg, en->key, en->value); + if (parse_ret < 0) + av_log(avctx, AV_LOG_WARNING, "Invalid value for %s: %s.\n", en->key, en->value); + } + av_dict_free(&dict); + } + } + + rret = rav1e_config_parse_int(cfg, "width", avctx->width); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Invalid width passed to rav1e.\n"); + ret = AVERROR_INVALIDDATA; + goto end; + } + + rret = rav1e_config_parse_int(cfg, "height", avctx->height); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Invalid width passed to rav1e.\n"); + ret = AVERROR_INVALIDDATA; + goto end; + } + + rret = rav1e_config_parse_int(cfg, "threads", avctx->thread_count); + if (rret < 0) + av_log(avctx, AV_LOG_WARNING, "Invalid number of threads, defaulting to auto.\n"); + + if (ctx->speed >= 0) { + rret = rav1e_config_parse_int(cfg, "speed", ctx->speed); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not set speed preset.\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + } + + if (ctx->tiles >= 0) { + rret = rav1e_config_parse_int(cfg, "tiles", ctx->speed); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not set number of tiles to encode with.\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + } + + if (avctx->gop_size > 0) { + rret = rav1e_config_parse_int(cfg, "key_frame_interval", avctx->gop_size); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not set max keyint.\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + } + + if (avctx->keyint_min > 0) { + rret = rav1e_config_parse_int(cfg, "min_key_frame_interval", avctx->keyint_min); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not set min keyint.\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + } + + if (avctx->bit_rate && ctx->quantizer < 0) { + int max_quantizer = avctx->qmax >= 0 ? avctx->qmax : 255; + + rret = rav1e_config_parse_int(cfg, "quantizer", max_quantizer); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not set max quantizer.\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + + if (avctx->qmin >= 0) { + rret = rav1e_config_parse_int(cfg, "min_quantizer", avctx->qmin); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not set min quantizer.\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + } + + rret = rav1e_config_parse_int(cfg, "bitrate", avctx->bit_rate); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not set bitrate.\n"); + ret = AVERROR_INVALIDDATA; + goto end; + } + } else if (ctx->quantizer >= 0) { + rret = rav1e_config_parse_int(cfg, "quantizer", ctx->quantizer); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not set quantizer.\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + } + + rret = rav1e_config_set_pixel_format(cfg, desc->comp[0].depth, + pix_fmt_map(avctx->pix_fmt), + chroma_loc_map(avctx->chroma_sample_location), + range_map(avctx->pix_fmt, avctx->color_range)); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to set pixel format properties.\n"); + ret = AVERROR_INVALIDDATA; + goto end; + } + + /* rav1e's colorspace enums match standard values. */ + rret = rav1e_config_set_color_description(cfg, (RaMatrixCoefficients) avctx->colorspace, + (RaColorPrimaries) avctx->color_primaries, + (RaTransferCharacteristics) avctx->color_trc); + if (rret < 0) { + av_log(avctx, AV_LOG_WARNING, "Failed to set color properties.\n"); + if (avctx->err_recognition & AV_EF_EXPLODE) { + ret = AVERROR_INVALIDDATA; + goto end; + } + } + + ctx->ctx = rav1e_context_new(cfg); + if (!ctx->ctx) { + av_log(avctx, AV_LOG_ERROR, "Failed to create rav1e encode context.\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + + ret = 0; + +end: + + rav1e_config_unref(cfg); + + return ret; +} + +static int librav1e_send_frame(AVCodecContext *avctx, const AVFrame *frame) +{ + librav1eContext *ctx = avctx->priv_data; + RaFrame *rframe = NULL; + int ret; + + if (frame) { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); + + rframe = rav1e_frame_new(ctx->ctx); + if (!rframe) { + av_log(avctx, AV_LOG_ERROR, "Could not allocate new rav1e frame.\n"); + return AVERROR(ENOMEM); + } + + for (int i = 0; i < desc->nb_components; i++) { + int shift = i ? desc->log2_chroma_h : 0; + int bytes = desc->comp[0].depth == 8 ? 1 : 2; + rav1e_frame_fill_plane(rframe, i, frame->data[i], + (frame->height >> shift) * frame->linesize[i], + frame->linesize[i], bytes); + } + } + + ret = rav1e_send_frame(ctx->ctx, rframe); + if (rframe) + rav1e_frame_unref(rframe); /* No need to unref if flushing. */ + + switch(ret) { + case RA_ENCODER_STATUS_SUCCESS: + break; + case RA_ENCODER_STATUS_ENOUGH_DATA: + return AVERROR(EAGAIN); + case RA_ENCODER_STATUS_FAILURE: + av_log(avctx, AV_LOG_ERROR, "Could not send frame.\n"); + return AVERROR_EXTERNAL; + default: + av_log(avctx, AV_LOG_ERROR, "Unknown return code from rav1e_send_frame.\n"); + return AVERROR_UNKNOWN; + } + + return 0; +} + +static void librav1e_packet_unref(void *opaque, uint8_t *data) +{ + RaPacket *rpkt = opaque; + + rav1e_packet_unref(rpkt); +} + +static int librav1e_receive_packet(AVCodecContext *avctx, AVPacket *pkt) +{ + librav1eContext *ctx = avctx->priv_data; + RaPacket *rpkt = NULL; + int ret; + +retry: + + if (avctx->flags & AV_CODEC_FLAG_PASS1) { + int sret = get_stats(avctx, 0); + if (sret < 0) + return sret; + } else if (avctx->flags & AV_CODEC_FLAG_PASS2) { + int sret = set_stats(avctx); + if (sret < 0) + return sret; + } + + ret = rav1e_receive_packet(ctx->ctx, &rpkt); + switch(ret) { + case RA_ENCODER_STATUS_SUCCESS: + break; + case RA_ENCODER_STATUS_LIMIT_REACHED: + if (avctx->flags & AV_CODEC_FLAG_PASS1) { + int sret = get_stats(avctx, 1); + if (sret < 0) + return sret; + } + return AVERROR_EOF; + case RA_ENCODER_STATUS_ENCODED: + if (avctx->internal->draining) + goto retry; + return AVERROR(EAGAIN); + case RA_ENCODER_STATUS_NEED_MORE_DATA: + if (avctx->internal->draining) { + av_log(avctx, AV_LOG_ERROR, "Unexpected error when receiving packet after EOF.\n"); + return AVERROR_EXTERNAL; + } + return AVERROR(EAGAIN); + case RA_ENCODER_STATUS_FAILURE: + av_log(avctx, AV_LOG_ERROR, "Could not encode frame.\n"); + return AVERROR_EXTERNAL; + default: + av_log(avctx, AV_LOG_ERROR, "Unknown return code %d from rav1e_receive_packet.\n", ret); + return AVERROR_UNKNOWN; + } + + pkt->buf = av_buffer_create((uint8_t *) rpkt->data, rpkt->len, librav1e_packet_unref, + rpkt, AV_BUFFER_FLAG_READONLY); + if (!pkt->buf) { + rav1e_packet_unref(rpkt); + return AVERROR(ENOMEM); + } + + pkt->data = (uint8_t *) rpkt->data; + pkt->size = rpkt->len; + + if (rpkt->frame_type == RA_FRAME_TYPE_KEY) + pkt->flags |= AV_PKT_FLAG_KEY; + + pkt->pts = pkt->dts = rpkt->input_frameno * avctx->ticks_per_frame; + + if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { + int ret = av_bsf_send_packet(ctx->bsf, pkt); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "extradata extraction send failed.\n"); + av_packet_unref(pkt); + return ret; + } + + ret = av_bsf_receive_packet(ctx->bsf, pkt); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "extradata extraction receive failed.\n"); + av_packet_unref(pkt); + return ret; + } + } + + return 0; +} + +#define OFFSET(x) offsetof(librav1eContext, x) +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM + +static const AVOption options[] = { + { "qp", "use constant quantizer mode", OFFSET(quantizer), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 255, VE }, + { "speed", "what speed preset to use", OFFSET(speed), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 9, VE }, + { "tiles", "number of tiles encode with", OFFSET(tiles), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT64_MAX, VE }, + { "rav1e-params", "set the rav1e configuration using a :-separated list of key=value parameters", OFFSET(rav1e_opts), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE }, + { NULL } +}; + +static const AVCodecDefault librav1e_defaults[] = { + { "g", "0" }, + { "keyint_min", "0" }, + { "qmax", "-1" }, + { "qmin", "-1" }, + { NULL } +}; + +const enum AVPixelFormat librav1e_pix_fmts[] = { + AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUVJ420P, + AV_PIX_FMT_YUV420P10, + AV_PIX_FMT_YUV420P12, + AV_PIX_FMT_YUV422P, + AV_PIX_FMT_YUVJ422P, + AV_PIX_FMT_YUV422P10, + AV_PIX_FMT_YUV422P12, + AV_PIX_FMT_YUV444P, + AV_PIX_FMT_YUVJ444P, + AV_PIX_FMT_YUV444P10, + AV_PIX_FMT_YUV444P12, + AV_PIX_FMT_NONE +}; + +static const AVClass class = { + .class_name = "librav1e", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVCodec ff_librav1e_encoder = { + .name = "librav1e", + .long_name = NULL_IF_CONFIG_SMALL("librav1e AV1"), + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_AV1, + .init = librav1e_encode_init, + .send_frame = librav1e_send_frame, + .receive_packet = librav1e_receive_packet, + .close = librav1e_encode_close, + .priv_data_size = sizeof(librav1eContext), + .priv_class = &class, + .defaults = librav1e_defaults, + .pix_fmts = librav1e_pix_fmts, + .capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AUTO_THREADS, + .caps_internal = FF_CODEC_CAP_INIT_CLEANUP, + .wrapper_name = "librav1e", +};
Port to the new send/receive API by: James Almer <jamrial@gmail.com>. Signed-off-by: Derek Buitenhuis <derek.buitenhuis@gmail.com> --- Lots of stuff happened since v3! * The C API / library is now in rav1e's main repo, and officially supported. * rav1e will bump the soname and pkg-config version on any breaking changes. * C API is now as fast as the Rust API. * Added two pass support. * Added min quantizer support. * Added tiles / speed to AVOptions. * Mapped min/max keyint. * Applied all the fixes requested in the last round. --- configure | 4 + doc/encoders.texi | 37 +++ doc/general.texi | 7 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/librav1e.c | 578 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 628 insertions(+) create mode 100644 libavcodec/librav1e.c