[FFmpeg-devel] [PATCH 2/3] avcodec/rv60: RealVideo 6.0 decoder
Andreas Rheinhardt
andreas.rheinhardt at outlook.com
Sun Mar 3 09:55:59 EET 2024
Peter Ross:
> Reviewed-by: Anton Khirnov <anton at khirnov.net>
> Reviewed-by: Andreas Rheinhardt <andreas.rheinhardt at outlook.com>
> Signed-off-by: Peter Ross <pross at xvid.org>
> ---
> libavcodec/Makefile | 1 +
> libavcodec/allcodecs.c | 1 +
> libavcodec/codec_desc.c | 7 +
> libavcodec/codec_id.h | 1 +
> libavcodec/rv60data.h | 118 ++
> libavcodec/rv60dec.c | 2419 +++++++++++++++++++++++++++++++++++++++
> libavcodec/rv60dsp.c | 164 +++
> libavcodec/rv60dsp.h | 30 +
> libavcodec/rv60vlcs.h | 2315 +++++++++++++++++++++++++++++++++++++
> libavformat/riff.c | 1 +
> libavformat/rm.c | 1 +
> 11 files changed, 5058 insertions(+)
> create mode 100644 libavcodec/rv60data.h
> create mode 100644 libavcodec/rv60dec.c
> create mode 100644 libavcodec/rv60dsp.c
> create mode 100644 libavcodec/rv60dsp.h
> create mode 100644 libavcodec/rv60vlcs.h
>
> diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
> index 033344304c..2ed6ce0953 100644
> --- a/libavcodec/codec_desc.c
> +++ b/libavcodec/codec_desc.c
> @@ -1967,6 +1967,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
> .long_name = NULL_IF_CONFIG_SMALL("LEAD MCMP"),
> .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
> },
> + {
> + .id = AV_CODEC_ID_RV60,
> + .type = AVMEDIA_TYPE_VIDEO,
> + .name = "rv60",
> + .long_name = NULL_IF_CONFIG_SMALL("RealVideo 6.0"),
> + .props = AV_CODEC_PROP_LOSSY,
Missing AV_CODEC_PROP_REORDER
> + },
>
> /* various PCM "codecs" */
> {
> diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
> index d96e49430e..3be0dda20e 100644
> --- a/libavcodec/codec_id.h
> +++ b/libavcodec/codec_id.h
> @@ -325,6 +325,7 @@ enum AVCodecID {
> AV_CODEC_ID_RTV1,
> AV_CODEC_ID_VMIX,
> AV_CODEC_ID_LEAD,
> + AV_CODEC_ID_RV60,
>
> /* various PCM "codecs" */
> AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs
> diff --git a/libavcodec/rv60data.h b/libavcodec/rv60data.h
> new file mode 100644
> index 0000000000..65f9853770
> --- /dev/null
> +++ b/libavcodec/rv60data.h
> @@ -0,0 +1,118 @@
> +/*
> + * RV60 decoder
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +#ifndef AVCODEC_RV60DATA_H
> +#define AVCODEC_RV60DATA_H
> +
> +#include <stdint.h>
> +
> +static const uint8_t rv60_candidate_intra_angles[6] = {
> + 0, 1, 10, 26, 18, 2
> +};
> +
> +static const uint8_t rv60_ipred_angle[9] = {
> + 0, 2, 5, 9, 13, 17, 21, 26, 32
> +};
> +
> +static const uint16_t rv60_ipred_inv_angle[9] = {
> + 0, 4096, 1638, 910, 630, 482, 390, 315, 256
> +};
> +
> +static const uint8_t rv60_avail_mask[64] = {
> + 0, 1, 0, 3, 0, 1, 0, 7, 0, 1, 0, 3, 0, 1, 0, 0xF,
> + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
> + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
> + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
> +};
> +
> +static const uint8_t rv60_edge1[4] = {
> + 0, 2, 2, 2
> +};
> +
> +static const uint8_t rv60_edge2[4] = {
> + 0, 3, 3, 3
> +};
> +
> +static const uint8_t rv60_qp_to_idx[64] = {
> + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3,
> + 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 0,
> + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
> + 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 0, 0
> +};
> +
> +static const uint16_t rv60_quants_b[32] = {
> + 60, 67, 76, 85, 96, 108, 121, 136,
> + 152, 171, 192, 216, 242, 272, 305, 341,
> + 383, 432, 481, 544, 606, 683, 767, 854,
> + 963, 1074, 1212, 1392, 1566, 1708, 1978, 2211
> +};
> +
> +static const uint16_t rv60_chroma_quant_dc[32] = {
> + 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
> + 14, 15, 15, 16, 17, 18, 18, 19, 20, 20, 21, 21, 22, 22, 23, 23
> +};
> +
> +static const uint16_t rv60_chroma_quant_ac[32] = {
> + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
> + 16, 17, 17, 18, 19, 20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 25
> +};
These two tables can use uint8_t. Or given that they are only used via
rv60_quants_b[rv60_chroma_quant_[ad]c[qp]] one could replace them with
uint16_t arrays that avoid the indirection.
...
> +static VLC cbp8_vlc[7][4];
> +static VLC cbp16_vlc[7][3][4];
> +
> +typedef struct {
> + VLC l0[2];
> + VLC l12[2];
> + VLC l3[2];
> + VLC esc;
> +} CoeffVLCs;
> +
> +static CoeffVLCs intra_coeff_vlc[5];
> +static CoeffVLCs inter_coeff_vlc[7];
> +
You do not need full VLC structures, only VLC.table is ever used,
because all VLCs here use nine bits. So you can replace e.g. cbp8_vlc by
static const VLCElem *cbp8_vlc[7][4] and switch to ff_vlc_init_tables()
(replace the int offset by VLCInitState for this).
> +#define MAX_VLC_SIZE 864
> +static VLCElem table_data[129148];
> +
> +/* 32-bit version of rv34_gen_vlc */
> +static void gen_vlc(const uint8_t *bits, int size, VLC *vlc, int *offset)
> +{
> + int counts[17] = {0};
> + uint32_t codes[18];
> + uint32_t cw[MAX_VLC_SIZE];
> +
> + for (int i = 0; i < size; i++)
> + counts[bits[i]]++;
> +
> + codes[0] = counts[0] = 0;
> + for (int i = 0; i < 17; i++)
> + codes[i+1] = (codes[i] + counts[i]) << 1;
> +
> + for (int i = 0; i < size; i++)
> + cw[i] = codes[bits[i]]++;
> +
> + vlc->table = &table_data[*offset];
> + vlc->table_allocated = FF_ARRAY_ELEMS(table_data) - *offset;
> + ff_vlc_init_sparse(vlc, 9, size,
> + bits, 1, 1,
> + cw, 4, 4,
> + NULL, 0, 0, VLC_INIT_STATIC_OVERLONG);
> + *offset += vlc->table_size;
> +}
> +
> +static void build_coeff_vlc(const CoeffLens * lens, CoeffVLCs * vlc, int count, int * offset)
> +{
> + for (int i = 0; i < count; i++) {
> + for (int j = 0; j < 2; j++) {
> + gen_vlc(lens[i].l0[j], 864, &vlc[i].l0[j], offset);
> + gen_vlc(lens[i].l12[j], 108, &vlc[i].l12[j], offset);
> + gen_vlc(lens[i].l3[j], 108, &vlc[i].l3[j], offset);
> + }
> + gen_vlc(lens[i].esc, 32, &vlc[i].esc, offset);
> + }
> +}
> +
> +static av_cold void rv60_init_static_data(void)
> +{
> + int offset = 0;
> +
> + for (int i = 0; i < 7; i++)
> + for (int j = 0; j < 4; j++)
> + gen_vlc(rv60_cbp8_lens[i][j], 64, &cbp8_vlc[i][j], &offset);
> +
> + for (int i = 0; i < 7; i++)
> + for (int j = 0; j < 3; j++)
> + for (int k = 0; k < 4; k++)
> + gen_vlc(rv60_cbp16_lens[i][j][k], 64, &cbp16_vlc[i][j][k], &offset);
> +
> + build_coeff_vlc(rv60_intra_lens, intra_coeff_vlc, 5, &offset);
> + build_coeff_vlc(rv60_inter_lens, inter_coeff_vlc, 7, &offset);
> +}
> +
> +typedef struct {
> + int sign;
> + int size;
> + const uint8_t * data;
> + int data_size;
> +} Slice;
> +
> +typedef struct {
> + int cu_split_pos;
> + uint8_t cu_split[1+4+16+64];
> +
> + uint8_t coded_blk[64];
> +
> + uint8_t avg_buffer[64*64 + 32*32*2];
> + AVFrame avg_buf;
Don't do this. Just store uint8_t *avg_buf[3] and int
avg_buf_linesize[3] and switch the two functions using avg_buf to accept
the data pointers and linesizes separately (for the output frame, not
for the ref frame).
> +} ThreadContext;
> +
> +typedef struct {
> + int16_t x;
> + int16_t y;
> +} MV;
> +
> +typedef struct {
> + enum MVRefEnum mvref;
> + MV f_mv;
> + MV b_mv;
> +} MVInfo;
> +
> +typedef struct {
> + enum IntraMode imode;
> + MVInfo mv;
> +} BlockInfo;
> +
> +typedef struct {
> + enum CUType cu_type;
> + enum PUType pu_type;
> +} PUInfo;
> +
> +typedef struct RV60Context {
> + AVCodecContext * avctx;
> + VideoDSPContext vdsp;
> +
> +#define CUR_PIC 0
> +#define LAST_PIC 1
> +#define NEXT_PIC 2
> + AVFrame *last_frame[3];
> +
> + int pict_type;
> + int qp;
> + int osvquant;
> + int ts;
> + int two_f_refs;
> + int qp_off_type;
> + int deblock;
> + int deblock_chroma;
> + int awidth;
> + int aheight;
> + int cu_width;
> + int cu_height;
> +
> + Slice * slice;
> +
> + int pu_stride;
> + PUInfo * pu_info;
> +
> + int blk_stride;
> + BlockInfo * blk_info;
> +
> + int dblk_stride;
> + uint8_t * left_str;
> + uint8_t * top_str;
> +
> + uint64_t ref_pts[2], ts_scale;
> + uint32_t ref_ts[2];
> +} RV60Context;
> +
> +static av_cold int rv60_decode_init(AVCodecContext * avctx)
> +{
> + static AVOnce init_static_once = AV_ONCE_INIT;
> + RV60Context *s = avctx->priv_data;
> + int ret;
> +
> + s->avctx = avctx;
> +
> + if (avctx->active_thread_type & FF_THREAD_SLICE) {
> + ret = ff_slice_thread_init_progress(avctx);
> + if (ret < 0)
> + return ret;
> + }
> +
> + ff_videodsp_init(&s->vdsp, 8);
Missing configure dependency for this
> +
> + avctx->pix_fmt = AV_PIX_FMT_YUV420P;
> +
> + for (int i = 0; i < 3; i++) {
> + s->last_frame[i] = av_frame_alloc();
> + if (!s->last_frame[i])
> + return AVERROR(ENOMEM);
> + }
> +
> + ff_thread_once(&init_static_once, rv60_init_static_data);
> +
> + return 0;
> +}
> +
...
> +
> +static int rv60_decode_frame(AVCodecContext *avctx, AVFrame * frame,
> + int * got_frame, AVPacket * avpkt)
> +{
> + RV60Context *s = avctx->priv_data;
> + GetBitContext gb;
> + int ret, header_size, width, height, ofs;
> +
> + if (avpkt->size == 0) {
> + if (s->last_frame[NEXT_PIC]->data[0]) {
> + av_frame_move_ref(frame, s->last_frame[NEXT_PIC]);
> + *got_frame = 1;
> + }
> + return 0;
> + }
> +
> + if (avpkt->size < 9)
> + return AVERROR_INVALIDDATA;
> +
> + header_size = avpkt->data[0] * 8 + 9;
> + if (avpkt->size < header_size)
> + return AVERROR_INVALIDDATA;
> +
> + init_get_bits8(&gb, avpkt->data + header_size, avpkt->size - header_size);
> +
> + if ((ret = read_frame_header(s, &gb, &width, &height)) < 0)
> + return ret;
> +
> + if (avctx->skip_frame >= AVDISCARD_NONREF && s->pict_type == AV_PICTURE_TYPE_B ||
> + avctx->skip_frame >= AVDISCARD_NONKEY && s->pict_type != AV_PICTURE_TYPE_I ||
> + avctx->skip_frame >= AVDISCARD_ALL)
> + return avpkt->size;
> +
> + if (s->pict_type != AV_PICTURE_TYPE_B)
> + FFSWAP(AVFrame *, s->last_frame[NEXT_PIC], s->last_frame[LAST_PIC]);
> +
> + if ((s->pict_type == AV_PICTURE_TYPE_P && !s->last_frame[LAST_PIC]->data[0]) ||
> + (s->pict_type == AV_PICTURE_TYPE_B && (!s->last_frame[LAST_PIC]->data[0] || !s->last_frame[NEXT_PIC]->data[0]))) {
> + av_log(s->avctx, AV_LOG_ERROR, "missing reference frame\n");
> + return AVERROR_INVALIDDATA;
> + }
> +
> + s->last_frame[CUR_PIC]->pict_type = s->pict_type;
> + if (s->pict_type == AV_PICTURE_TYPE_I)
> + s->last_frame[CUR_PIC]->flags |= AV_FRAME_FLAG_KEY;
> +
> + if ((ret = update_dimensions_clear_info(s, width, height)) < 0)
> + return ret;
> +
> + if ((ret = ff_reget_buffer(avctx, s->last_frame[CUR_PIC], 0)) < 0)
> + return ret;
ff_reget_buffer() might have to perform a full-frame copy. Why do you
use it?
> +
> + if ((ret = read_slice_sizes(s, &gb)) < 0)
> + return ret;
> +
> + ofs = get_bits_count(&gb) / 8;
> +
> + for (int i = 0; i < s->cu_height; i++) {
> + s->slice[i].data = avpkt->data + header_size + ofs;
> + s->slice[i].data_size = FFMIN(s->slice[i].size, avpkt->size - header_size - ofs);
> + ofs += s->slice[i].size;
> + }
> +
> + if (ffcodec(avctx->codec)->update_thread_context)
> + ff_thread_finish_setup(avctx);
This seems to have been copied from VP8; but the check is always false
here and ff_thread_finish_setup is unnecessary, as this decoder does not
support frame threads.
> +
> + ret = ff_slice_thread_allocz_entries(s->avctx, s->cu_height);
> + if (ret < 0)
> + return ret;
> +
> + s->avctx->execute2(s->avctx, decode_slice, s->last_frame[CUR_PIC], NULL, s->cu_height);
> +
> + ret = 0;
> + if (s->pict_type == AV_PICTURE_TYPE_B)
> + av_frame_move_ref(frame, s->last_frame[CUR_PIC]);
> + else if (s->last_frame[LAST_PIC]->data[0])
> + ret = av_frame_ref(frame, s->last_frame[LAST_PIC]);
> + if (ret < 0)
> + return ret;
> +
> + if (s->last_frame[LAST_PIC]->data[0])
Why not check for frame->data[0]? Seems clearer to me. And it would work
if one implemented low-delay (i.e. where it is presumed that no b-frames
are present).
> + *got_frame = 1;
> +
> + if (s->pict_type != AV_PICTURE_TYPE_B)
> + FFSWAP(AVFrame *, s->last_frame[CUR_PIC], s->last_frame[NEXT_PIC]);
> +
> + if (s->pict_type != AV_PICTURE_TYPE_B) {
> + s->ref_pts[0] = s->ref_pts[1];
> + s->ref_pts[1] = avpkt->pts;
> +
> + s->ref_ts[0] = s->ref_ts[1];
> + s->ref_ts[1] = s->ts;
> +
> + if (s->ref_pts[1] > s->ref_pts[0] && s->ref_ts[1] > s->ref_ts[0])
> + s->ts_scale = (s->ref_pts[1] - s->ref_pts[0]) / (s->ref_ts[1] - s->ref_ts[0]);
> + } else {
> + frame->pts = s->ref_pts[0] + (s->ts - s->ref_ts[0]) * s->ts_scale;
> + }
> +
> + return avpkt->size;
> +}
> +
> +static void rv60_flush(AVCodecContext *avctx)
> +{
> + RV60Context *s = avctx->priv_data;
> +
> + for (int i = 0; i < 3; i++)
> + av_frame_unref(s->last_frame[i]);
> +}
> +
> +static av_cold int rv60_decode_end(AVCodecContext * avctx)
> +{
> + RV60Context *s = avctx->priv_data;
> +
> + for (int i = 0; i < 3; i++)
> + av_frame_free(&s->last_frame[i]);
> +
> + av_freep(&s->slice);
> + av_freep(&s->pu_info);
> + av_freep(&s->blk_info);
> + av_freep(&s->top_str);
> + av_freep(&s->left_str);
> +
> + return 0;
> +}
> +
> +const FFCodec ff_rv60_decoder = {
> + .p.name = "rv60",
> + CODEC_LONG_NAME("RealVideo 6.0"),
> + .p.type = AVMEDIA_TYPE_VIDEO,
> + .p.id = AV_CODEC_ID_RV60,
> + .priv_data_size = sizeof(RV60Context),
> + .init = rv60_decode_init,
> + .close = rv60_decode_end,
> + FF_CODEC_DECODE_CB(rv60_decode_frame),
> + .flush = rv60_flush,
> + .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS,
> + .caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
> +};
More information about the ffmpeg-devel
mailing list