Message ID | 20190528172916.123586-1-derek.buitenhuis@gmail.com |
---|---|
State | Superseded |
Headers | show |
On 5/28/2019 2:29 PM, Derek Buitenhuis wrote: > Uses the crav1e C bindings for rav1e. > > Missing version bump and changelog entry. > > Signed-off-by: Derek Buitenhuis <derek.buitenhuis@gmail.com> > --- > Hoping to get some eyes on this, and maybe help some people who want to > test out rav1e without having to use Y4Ms or pipes. > > Some points: > * The C bindings for rav1e currently live in the crav1e repo. This will > be merged into rav1e's repo Soon(TM), when stuff stabalizes. > * I have been told that I could ditch the frame queue if I used the 'new' > send_frame / recieve_packet API. I tried this and ran into odd issues > like it trying to flush too often (mid stream, etc.). I may have bungled > it, but it didn't seem to work, and no non-hw encoder uses this API yet. > Since libaomenc also has this sot of queue sytem, I decided to play it > safe. > --- > configure | 4 + > doc/encoders.texi | 29 +++ > doc/general.texi | 7 + > libavcodec/Makefile | 1 + > libavcodec/allcodecs.c | 1 + > libavcodec/librav1e.c | 451 +++++++++++++++++++++++++++++++++++++++++ > 6 files changed, 493 insertions(+) > create mode 100644 libavcodec/librav1e.c > > diff --git a/configure b/configure > index 32fc26356c..e61644b012 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 > @@ -3173,6 +3175,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" > @@ -6199,6 +6202,7 @@ enabled libopus && { > } > } > enabled libpulse && require_pkg_config libpulse libpulse pulse/pulseaudio.h pa_context_new > +enabled librav1e && require_pkg_config librav1e rav1e 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..2ed7053838 100644 > --- a/doc/encoders.texi > +++ b/doc/encoders.texi > @@ -1378,6 +1378,35 @@ 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 from crav1e > +during configuration. You need to explicitly configue the build with > +@code{--enable-librav1e}. > + > +@subsection Options > + > +@table @option > +@item max-quantizer > +Sets the maximum qauntizer (floor) to use when using bitrate mode. > + > +@item quantizer > +Uses quantizers mode to encode at the given quantizer. > + > +@item rav1e-params > +Set rav1e options using a list of @var{key}=@var{value} couples 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 -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 ec437230e3..6a1cd11a22 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 @url{https://github.com/xiph/rav1e/} > +and follow the instructions. 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 edccd73037..dc589ce35d 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 6178d31b5c..8a59f39a90 100644 > --- a/libavcodec/allcodecs.c > +++ b/libavcodec/allcodecs.c > @@ -702,6 +702,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..30ca7e509e > --- /dev/null > +++ b/libavcodec/librav1e.c > @@ -0,0 +1,451 @@ > +/* > + * 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/common.h" > +#include "libavutil/opt.h" > +#include "libavutil/pixdesc.h" > +#include "avcodec.h" > +#include "internal.h" > + > +typedef struct PacketList { > + RaPacket *pkt; > + struct PacketList *next; > +} PacketList; > + > +typedef struct librav1eContext { > + const AVClass *class; > + > + RaContext *ctx; > + AVBSFContext *bsf; > + PacketList *pktlist; > + char *rav1e_opts; > + int max_quantizer; > + int quantizer; > + int done; > +} librav1eContext; > + > +static inline RaPixelRange range_map(enum AVColorRange range) > + > +{ > + 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_YUV420P10: > + case AV_PIX_FMT_YUV420P12: > + return RA_CHROMA_SAMPLING_CS420; > + case AV_PIX_FMT_YUV422P: > + case AV_PIX_FMT_YUV422P10: > + case AV_PIX_FMT_YUV422P12: > + return RA_CHROMA_SAMPLING_CS422; > + case AV_PIX_FMT_YUV444P: > + case AV_PIX_FMT_YUV444P10: > + case AV_PIX_FMT_YUV444P12: > + return RA_CHROMA_SAMPLING_CS444; > + case AV_PIX_FMT_GRAY8: > + case AV_PIX_FMT_GRAY10: > + case AV_PIX_FMT_GRAY12: > + return RA_CHROMA_SAMPLING_CS400; > + default: > + // This should be impossible > + return (RaChromaSampling) -1; If it's not meant to happen, then it should probably be an av_assert0. > + } > +} > + > +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 add_packet(PacketList **list, RaPacket *pkt) > +{ > + PacketList *cur = *list; > + PacketList *newentry = av_mallocz(sizeof(PacketList)); > + if (!newentry) > + return AVERROR(ENOMEM); > + > + newentry->pkt = pkt; > + > + if (!cur) { > + *list = newentry; > + return 0; > + } > + > + /* > + * Just use a simple linear search, since the reoroder buffer in > + * AV1 is capped to something fairly low. > + */ > + while (cur->next) > + cur = cur->next; > + > + cur->next = newentry; > + > + return 0; > +} > + > +static RaPacket *get_packet(PacketList **list) > +{ > + PacketList *head = *list; > + RaPacket *ret; > + > + if (!head) > + return NULL; > + > + ret = head->pkt; > + > + *list = head->next; > + av_free(head); > + > + return ret; > +} > + > +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; > + } > + > + while (ctx->pktlist) { > + RaPacket *rpkt = get_packet(&ctx->pktlist); > + rav1e_packet_unref(rpkt); > + } > + > + av_bsf_free(&ctx->bsf); > + > + 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; Why two ret variables? > + > + cfg = rav1e_config_default(); > + if (!cfg) { > + av_log(avctx, AV_LOG_ERROR, "Could not allocate rav1e config.\n"); > + ret = AVERROR_EXTERNAL; > + goto end; > + } > + > + if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { > + const AVBitStreamFilter *filter = av_bsf_get_by_name("extract_extradata"); See https://github.com/dwbuiten/FFmpeg/commit/e146749232928a1cebe637338189938644237894#r33622810 You don't need to extract the Sequence Header from encoded frames since rav1e provides a function for this purpose. So does libaom, for that matter, but it's buggy and that's why it's not being used. > + 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 (avctx->bit_rate && ctx->quantizer < 0) { > + rret = rav1e_config_parse_int(cfg, "quantizer", ctx->max_quantizer); > + if (rret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not set max 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->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: > + if (cfg) > + rav1e_config_unref(cfg); > + > + if (ret) > + librav1e_encode_close(avctx); > + > + return ret; > +} > + > +static int librav1e_encode_frame(AVCodecContext *avctx, AVPacket *pkt, > + const AVFrame *pic, int *got_packet) > +{ > + librav1eContext *ctx = avctx->priv_data; > + RaPacket *rpkt = NULL; > + RaFrame *rframe = NULL; > + RaEncoderStatus ret; > + int pret; > + > + if (pic) { > + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pic->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 < 3; i++) { > + int shift = i ? desc->log2_chroma_h : 0; > + int bytes = desc->comp[0].depth == 8 ? 1 : 2; > + rav1e_frame_fill_plane(rframe, i, pic->data[i], > + (pic->height >> shift) * pic->linesize[i], > + pic->linesize[i], bytes); > + } > + } > + > + ret = rav1e_send_frame(ctx->ctx, rframe); > + if (rframe) > + rav1e_frame_unref(rframe); /* No need to unref if flushing. */ > + if (ret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not send frame.\n"); > + return AVERROR_EXTERNAL; > + } else if (ret == RA_ENCODER_STATUS_ENOUGH_DATA) { > + av_log(avctx, AV_LOG_WARNING, "rav1e encode queue is full. Frames may be dropped.\n"); > + } > + > + while (!ctx->done) { > + ret = rav1e_receive_packet(ctx->ctx, &rpkt); > + if (ret < 0) { > + av_log(avctx, AV_LOG_ERROR, "Could not encode frame.\n"); > + return AVERROR_EXTERNAL; > + } else if (ret == RA_ENCODER_STATUS_NEED_MORE_DATA || ret == RA_ENCODER_STATUS_ENCODED) { > + break; > + } else if (ret == RA_ENCODER_STATUS_LIMIT_REACHED) { > + /* We're done. Nothing else to flush, so stop tryng. */ > + ctx->done = 1; > + break; > + } else if (ret == RA_ENCODER_STATUS_SUCCESS) { > + /* > + * Since we must drain the encoder of packets when we finally successfully > + * receive one, add it to a packet queue and output as need be. Since this > + * is only due to the frame "reordering" (alt-ref) internal to the encoder, > + * it should never get very big before all being output. This is is similar > + * to what is done in libaomenc.c. > + */ > + int aret = add_packet(&ctx->pktlist, rpkt); > + if (aret < 0) { > + rav1e_packet_unref(rpkt); > + return aret; > + } > + rpkt = NULL; > + } else { > + av_log(avctx, AV_LOG_ERROR, "Unknown return code from ra1ve_receive_packet.\n"); > + return AVERROR_UNKNOWN; > + } > + } > + > + rpkt = get_packet(&ctx->pktlist); > + if (!rpkt) > + return 0; > + > + pret = ff_alloc_packet2(avctx, pkt, rpkt->len, rpkt->len); > + if (pret < 0) { > + rav1e_packet_unref(rpkt); > + av_log(avctx, AV_LOG_ERROR, "Error getting output packet.\n"); > + return ret; > + } > + > + memcpy(pkt->data, rpkt->data, rpkt->len); > + > + if (rpkt->frame_type == RA_FRAME_TYPE_KEY) > + pkt->flags |= AV_PKT_FLAG_KEY; > + > + pkt->pts = pkt->dts = rpkt->number * avctx->ticks_per_frame; > + > + rav1e_packet_unref(rpkt); You can avoid the data copy if you wrap the RaPacket into the AVBufferRef used in the AVPacket, but for this you need to use the send/receive API, given the encode2 one allows the caller to use their own allocated packet. > + > + if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { > + pret = av_bsf_send_packet(ctx->bsf, pkt); > + if (pret < 0) { > + av_log(avctx, AV_LOG_ERROR, "extradata extraction send failed.\n"); > + return pret; > + } > + > + pret = av_bsf_receive_packet(ctx->bsf, pkt); > + if (pret < 0) { > + av_log(avctx, AV_LOG_ERROR, "extradata extraction receive failed.\n"); > + return pret; > + } > + } > + > + *got_packet = 1; > + > + return 0; > +} > + > +#define OFFSET(x) offsetof(librav1eContext, x) > +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM > + > +static const AVOption options[] = { > + { "quantizer", "use constant quantizer mode", OFFSET(quantizer), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 255, VE }, > + { "max-quantizer", "max quantizer when using bitrate mode", OFFSET(max_quantizer), AV_OPT_TYPE_INT, { .i64 = 255 }, 1, 255, VE }, This should be mapped to qmax lavc option instead. > + { "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 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, > + .encode2 = librav1e_encode_frame, > + .close = librav1e_encode_close, > + .priv_data_size = sizeof(librav1eContext), > + .priv_class = &class, > + .pix_fmts = (const enum AVPixelFormat[]) { > + AV_PIX_FMT_YUV420P, > + AV_PIX_FMT_YUV420P10, > + AV_PIX_FMT_YUV420P12, > + AV_PIX_FMT_YUV422P, > + AV_PIX_FMT_YUV422P10, > + AV_PIX_FMT_YUV422P12, > + AV_PIX_FMT_YUV444P, > + AV_PIX_FMT_YUV444P10, > + AV_PIX_FMT_YUV444P12, > + AV_PIX_FMT_GRAY8, > + AV_PIX_FMT_GRAY10, > + AV_PIX_FMT_GRAY12, > + AV_PIX_FMT_NONE > + }, > + .capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AUTO_THREADS, > + .wrapper_name = "librav1e", > +}; >
On 28/05/2019 20:32, James Almer wrote: >> + default: >> + // This should be impossible >> + return (RaChromaSampling) -1; > > If it's not meant to happen, then it should probably be an av_assert0. Yeah, that makes more sense. Will change. >> + int rret; >> + int ret = 0; > > Why two ret variables? Will fix. >> + if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { >> + const AVBitStreamFilter *filter = av_bsf_get_by_name("extract_extradata"); > > See > https://github.com/dwbuiten/FFmpeg/commit/e146749232928a1cebe637338189938644237894#r33622810 > > You don't need to extract the Sequence Header from encoded frames since > rav1e provides a function for this purpose. > So does libaom, for that matter, but it's buggy and that's why it's not > being used. As discussed on IRC, that function does not return the config OBUs at the end of the ISOBMFF-style data, so this way is still preferred until it does. I'll switch over if/once rav1e's API does return that. >> + memcpy(pkt->data, rpkt->data, rpkt->len); >> + >> + if (rpkt->frame_type == RA_FRAME_TYPE_KEY) >> + pkt->flags |= AV_PKT_FLAG_KEY; >> + >> + pkt->pts = pkt->dts = rpkt->number * avctx->ticks_per_frame; >> + >> + rav1e_packet_unref(rpkt); > > You can avoid the data copy if you wrap the RaPacket into the > AVBufferRef used in the AVPacket, but for this you need to use the > send/receive API, given the encode2 one allows the caller to use their > own allocated packet. As discussed on IRC / noted in the top of this email, I'll see how you fare switching to send/recieve. :) >> +static const AVOption options[] = { >> + { "quantizer", "use constant quantizer mode", OFFSET(quantizer), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 255, VE }, >> + { "max-quantizer", "max quantizer when using bitrate mode", OFFSET(max_quantizer), AV_OPT_TYPE_INT, { .i64 = 255 }, 1, 255, VE }, > > This should be mapped to qmax lavc option instead. OK. Is there a matching option for plain old quantizer? I couldn't find an obvious one. - Derk
On 5/28/2019 4:49 PM, Derek Buitenhuis wrote: > On 28/05/2019 20:32, James Almer wrote: >>> + default: >>> + // This should be impossible >>> + return (RaChromaSampling) -1; >> >> If it's not meant to happen, then it should probably be an av_assert0. > > Yeah, that makes more sense. Will change. > >>> + int rret; >>> + int ret = 0; >> >> Why two ret variables? > > Will fix. > >>> + if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { >>> + const AVBitStreamFilter *filter = av_bsf_get_by_name("extract_extradata"); >> >> See >> https://github.com/dwbuiten/FFmpeg/commit/e146749232928a1cebe637338189938644237894#r33622810 >> >> You don't need to extract the Sequence Header from encoded frames since >> rav1e provides a function for this purpose. >> So does libaom, for that matter, but it's buggy and that's why it's not >> being used. > > As discussed on IRC, that function does not return the config OBUs at the end of > the ISOBMFF-style data, so this way is still preferred until it does. > > I'll switch over if/once rav1e's API does return that. > > >>> + memcpy(pkt->data, rpkt->data, rpkt->len); >>> + >>> + if (rpkt->frame_type == RA_FRAME_TYPE_KEY) >>> + pkt->flags |= AV_PKT_FLAG_KEY; >>> + >>> + pkt->pts = pkt->dts = rpkt->number * avctx->ticks_per_frame; >>> + >>> + rav1e_packet_unref(rpkt); >> >> You can avoid the data copy if you wrap the RaPacket into the >> AVBufferRef used in the AVPacket, but for this you need to use the >> send/receive API, given the encode2 one allows the caller to use their >> own allocated packet. > > As discussed on IRC / noted in the top of this email, I'll see how > you fare switching to send/recieve. :) > >>> +static const AVOption options[] = { >>> + { "quantizer", "use constant quantizer mode", OFFSET(quantizer), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 255, VE }, >>> + { "max-quantizer", "max quantizer when using bitrate mode", OFFSET(max_quantizer), AV_OPT_TYPE_INT, { .i64 = 255 }, 1, 255, VE }, >> >> This should be mapped to qmax lavc option instead. > > OK. Is there a matching option for plain old quantizer? > I couldn't find an obvious one. I think x26* and vpx/aom call it crf? It's not in option_tables.h in any case. > > - Derk > _______________________________________________ > 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 28/05/2019 20:49, Derek Buitenhuis wrote: >>> +static const AVOption options[] = { >>> + { "quantizer", "use constant quantizer mode", OFFSET(quantizer), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 255, VE }, >>> + { "max-quantizer", "max quantizer when using bitrate mode", OFFSET(max_quantizer), AV_OPT_TYPE_INT, { .i64 = 255 }, 1, 255, VE }, >> This should be mapped to qmax lavc option instead. > OK. Is there a matching option for plain old quantizer? > I couldn't find an obvious one. Looking closer at this, it does not seem correct to use it. As, per `ffmpeg -h full`: -qmax <int> E..V..... maximum video quantizer scale (VBR) (from -1 to 1024) (default 31) The default of 31 is really bad for AV1. Further, rav1e's CLI default of 255 is probably the safest to use here. - Derek
On 28/05/2019 20:58, James Almer wrote: > I think x26* and vpx/aom call it crf? It's not in option_tables.h in any > case. They do not. This is a constant quantizer mode, not constant rate factor. - Derek
On 2019-05-28 22:00, Derek Buitenhuis wrote: > On 28/05/2019 20:58, James Almer wrote: >> I think x26* and vpx/aom call it crf? It's not in option_tables.h in any >> case. > > They do not. This is a constant quantizer mode, not constant rate factor. IIRC either qp or cqp
Am Di., 28. Mai 2019 um 19:29 Uhr schrieb Derek Buitenhuis <derek.buitenhuis@gmail.com>: > * The C bindings for rav1e currently live in the crav1e repo. This will > be merged into rav1e's repo Soon(TM), when stuff stabalizes. So is your patch meant to wait until this merge is done? I would suggest so... Carl Eugen
On 31/05/2019 23:08, Carl Eugen Hoyos wrote: > So is your patch meant to wait until this merge is done? > I would suggest so... Soon has a (TM) beside it for a reason (there needs to be some work done on Rust itself first, I think.) I would expect it to take at least 1-2 months. The API itself won't change. While I prefer to merge it sooner rather than later to aid in peoples' use of rav1e, I'm open to others opinions here. - Derek
Am So., 2. Juni 2019 um 19:54 Uhr schrieb Derek Buitenhuis <derek.buitenhuis@gmail.com>: > > On 31/05/2019 23:08, Carl Eugen Hoyos wrote: > > So is your patch meant to wait until this merge is done? > > I would suggest so... > > Soon has a (TM) beside it for a reason (there needs to be some > work done on Rust itself first, I think.) I would expect it to > take at least 1-2 months. Since a lot of unexpected changes can happen after months, this sounds to me like a strong reason to wait. Carl Eugen
On 6/2/19, Carl Eugen Hoyos <ceffmpeg@gmail.com> wrote: > Am So., 2. Juni 2019 um 19:54 Uhr schrieb Derek Buitenhuis > <derek.buitenhuis@gmail.com>: >> >> On 31/05/2019 23:08, Carl Eugen Hoyos wrote: >> > So is your patch meant to wait until this merge is done? >> > I would suggest so... >> >> Soon has a (TM) beside it for a reason (there needs to be some >> work done on Rust itself first, I think.) I would expect it to >> take at least 1-2 months. > > Since a lot of unexpected changes can happen after months, > this sounds to me like a strong reason to wait. > Why, could you elaborate?
On Sun, Jun 02, 2019 at 18:46:17 +0100, Derek Buitenhuis wrote:
> take at least 1-2 months. The API itself won't change.
https://github.com/lu-zero/crav1e says:
Status
The API is far from stable
Just sayin',
Moritz
On 02/06/2019 19:45, Moritz Barsnick wrote: > https://github.com/lu-zero/crav1e says: > > Status > The API is far from stable Yeah, that's not super true any more, and I'll remove it. - Derek
On 02/06/2019 19:27, Carl Eugen Hoyos wrote: > Since a lot of unexpected changes can happen after months, > this sounds to me like a strong reason to wait. This has nothing to do with API changes, it's a Rust toolchain (build system, specifically) thing. These changes don't change API/ABI. - Derek
On 02/06/2019 21:19, Derek Buitenhuis wrote: > This has nothing to do with API changes, it's a Rust toolchain (build > system, specifically) thing. These changes don't change API/ABI. Also to add: We will probably be cutting a 'real' rav1e release soon (0.1.0), I think. That could also be a place to settle on for merging the patch. I'll bring it up in the weekly Xiph/Daala/rav1e meeting on Tuesday. - Derek
diff --git a/configure b/configure index 32fc26356c..e61644b012 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 @@ -3173,6 +3175,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" @@ -6199,6 +6202,7 @@ enabled libopus && { } } enabled libpulse && require_pkg_config libpulse libpulse pulse/pulseaudio.h pa_context_new +enabled librav1e && require_pkg_config librav1e rav1e 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..2ed7053838 100644 --- a/doc/encoders.texi +++ b/doc/encoders.texi @@ -1378,6 +1378,35 @@ 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 from crav1e +during configuration. You need to explicitly configue the build with +@code{--enable-librav1e}. + +@subsection Options + +@table @option +@item max-quantizer +Sets the maximum qauntizer (floor) to use when using bitrate mode. + +@item quantizer +Uses quantizers mode to encode at the given quantizer. + +@item rav1e-params +Set rav1e options using a list of @var{key}=@var{value} couples 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 -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 ec437230e3..6a1cd11a22 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 @url{https://github.com/xiph/rav1e/} +and follow the instructions. 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 edccd73037..dc589ce35d 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 6178d31b5c..8a59f39a90 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -702,6 +702,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..30ca7e509e --- /dev/null +++ b/libavcodec/librav1e.c @@ -0,0 +1,451 @@ +/* + * 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/common.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avcodec.h" +#include "internal.h" + +typedef struct PacketList { + RaPacket *pkt; + struct PacketList *next; +} PacketList; + +typedef struct librav1eContext { + const AVClass *class; + + RaContext *ctx; + AVBSFContext *bsf; + PacketList *pktlist; + char *rav1e_opts; + int max_quantizer; + int quantizer; + int done; +} librav1eContext; + +static inline RaPixelRange range_map(enum AVColorRange range) + +{ + 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_YUV420P10: + case AV_PIX_FMT_YUV420P12: + return RA_CHROMA_SAMPLING_CS420; + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUV422P10: + case AV_PIX_FMT_YUV422P12: + return RA_CHROMA_SAMPLING_CS422; + case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUV444P10: + case AV_PIX_FMT_YUV444P12: + return RA_CHROMA_SAMPLING_CS444; + case AV_PIX_FMT_GRAY8: + case AV_PIX_FMT_GRAY10: + case AV_PIX_FMT_GRAY12: + return RA_CHROMA_SAMPLING_CS400; + default: + // This should be impossible + return (RaChromaSampling) -1; + } +} + +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 add_packet(PacketList **list, RaPacket *pkt) +{ + PacketList *cur = *list; + PacketList *newentry = av_mallocz(sizeof(PacketList)); + if (!newentry) + return AVERROR(ENOMEM); + + newentry->pkt = pkt; + + if (!cur) { + *list = newentry; + return 0; + } + + /* + * Just use a simple linear search, since the reoroder buffer in + * AV1 is capped to something fairly low. + */ + while (cur->next) + cur = cur->next; + + cur->next = newentry; + + return 0; +} + +static RaPacket *get_packet(PacketList **list) +{ + PacketList *head = *list; + RaPacket *ret; + + if (!head) + return NULL; + + ret = head->pkt; + + *list = head->next; + av_free(head); + + return ret; +} + +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; + } + + while (ctx->pktlist) { + RaPacket *rpkt = get_packet(&ctx->pktlist); + rav1e_packet_unref(rpkt); + } + + av_bsf_free(&ctx->bsf); + + 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"); + ret = AVERROR_EXTERNAL; + 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 (avctx->bit_rate && ctx->quantizer < 0) { + rret = rav1e_config_parse_int(cfg, "quantizer", ctx->max_quantizer); + if (rret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not set max 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->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: + if (cfg) + rav1e_config_unref(cfg); + + if (ret) + librav1e_encode_close(avctx); + + return ret; +} + +static int librav1e_encode_frame(AVCodecContext *avctx, AVPacket *pkt, + const AVFrame *pic, int *got_packet) +{ + librav1eContext *ctx = avctx->priv_data; + RaPacket *rpkt = NULL; + RaFrame *rframe = NULL; + RaEncoderStatus ret; + int pret; + + if (pic) { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pic->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 < 3; i++) { + int shift = i ? desc->log2_chroma_h : 0; + int bytes = desc->comp[0].depth == 8 ? 1 : 2; + rav1e_frame_fill_plane(rframe, i, pic->data[i], + (pic->height >> shift) * pic->linesize[i], + pic->linesize[i], bytes); + } + } + + ret = rav1e_send_frame(ctx->ctx, rframe); + if (rframe) + rav1e_frame_unref(rframe); /* No need to unref if flushing. */ + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not send frame.\n"); + return AVERROR_EXTERNAL; + } else if (ret == RA_ENCODER_STATUS_ENOUGH_DATA) { + av_log(avctx, AV_LOG_WARNING, "rav1e encode queue is full. Frames may be dropped.\n"); + } + + while (!ctx->done) { + ret = rav1e_receive_packet(ctx->ctx, &rpkt); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not encode frame.\n"); + return AVERROR_EXTERNAL; + } else if (ret == RA_ENCODER_STATUS_NEED_MORE_DATA || ret == RA_ENCODER_STATUS_ENCODED) { + break; + } else if (ret == RA_ENCODER_STATUS_LIMIT_REACHED) { + /* We're done. Nothing else to flush, so stop tryng. */ + ctx->done = 1; + break; + } else if (ret == RA_ENCODER_STATUS_SUCCESS) { + /* + * Since we must drain the encoder of packets when we finally successfully + * receive one, add it to a packet queue and output as need be. Since this + * is only due to the frame "reordering" (alt-ref) internal to the encoder, + * it should never get very big before all being output. This is is similar + * to what is done in libaomenc.c. + */ + int aret = add_packet(&ctx->pktlist, rpkt); + if (aret < 0) { + rav1e_packet_unref(rpkt); + return aret; + } + rpkt = NULL; + } else { + av_log(avctx, AV_LOG_ERROR, "Unknown return code from ra1ve_receive_packet.\n"); + return AVERROR_UNKNOWN; + } + } + + rpkt = get_packet(&ctx->pktlist); + if (!rpkt) + return 0; + + pret = ff_alloc_packet2(avctx, pkt, rpkt->len, rpkt->len); + if (pret < 0) { + rav1e_packet_unref(rpkt); + av_log(avctx, AV_LOG_ERROR, "Error getting output packet.\n"); + return ret; + } + + memcpy(pkt->data, rpkt->data, rpkt->len); + + if (rpkt->frame_type == RA_FRAME_TYPE_KEY) + pkt->flags |= AV_PKT_FLAG_KEY; + + pkt->pts = pkt->dts = rpkt->number * avctx->ticks_per_frame; + + rav1e_packet_unref(rpkt); + + if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { + pret = av_bsf_send_packet(ctx->bsf, pkt); + if (pret < 0) { + av_log(avctx, AV_LOG_ERROR, "extradata extraction send failed.\n"); + return pret; + } + + pret = av_bsf_receive_packet(ctx->bsf, pkt); + if (pret < 0) { + av_log(avctx, AV_LOG_ERROR, "extradata extraction receive failed.\n"); + return pret; + } + } + + *got_packet = 1; + + return 0; +} + +#define OFFSET(x) offsetof(librav1eContext, x) +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM + +static const AVOption options[] = { + { "quantizer", "use constant quantizer mode", OFFSET(quantizer), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 255, VE }, + { "max-quantizer", "max quantizer when using bitrate mode", OFFSET(max_quantizer), AV_OPT_TYPE_INT, { .i64 = 255 }, 1, 255, 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 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, + .encode2 = librav1e_encode_frame, + .close = librav1e_encode_close, + .priv_data_size = sizeof(librav1eContext), + .priv_class = &class, + .pix_fmts = (const enum AVPixelFormat[]) { + AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUV420P10, + AV_PIX_FMT_YUV420P12, + AV_PIX_FMT_YUV422P, + AV_PIX_FMT_YUV422P10, + AV_PIX_FMT_YUV422P12, + AV_PIX_FMT_YUV444P, + AV_PIX_FMT_YUV444P10, + AV_PIX_FMT_YUV444P12, + AV_PIX_FMT_GRAY8, + AV_PIX_FMT_GRAY10, + AV_PIX_FMT_GRAY12, + AV_PIX_FMT_NONE + }, + .capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AUTO_THREADS, + .wrapper_name = "librav1e", +};
Uses the crav1e C bindings for rav1e. Missing version bump and changelog entry. Signed-off-by: Derek Buitenhuis <derek.buitenhuis@gmail.com> --- Hoping to get some eyes on this, and maybe help some people who want to test out rav1e without having to use Y4Ms or pipes. Some points: * The C bindings for rav1e currently live in the crav1e repo. This will be merged into rav1e's repo Soon(TM), when stuff stabalizes. * I have been told that I could ditch the frame queue if I used the 'new' send_frame / recieve_packet API. I tried this and ran into odd issues like it trying to flush too often (mid stream, etc.). I may have bungled it, but it didn't seem to work, and no non-hw encoder uses this API yet. Since libaomenc also has this sot of queue sytem, I decided to play it safe. --- configure | 4 + doc/encoders.texi | 29 +++ doc/general.texi | 7 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/librav1e.c | 451 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 493 insertions(+) create mode 100644 libavcodec/librav1e.c