[FFmpeg-devel] [PATCH] Efficiently support several output pixel formats in Cinepak decoder

wm4 nfxjfg at googlemail.com
Thu Feb 2 17:25:05 EET 2017


On Wed, 1 Feb 2017 20:37:02 +0100
u-9iep at aetey.se wrote:

> From 300b8a4e712d1a404983b245aac501e09326ee72 Mon Sep 17 00:00:00 2001
> From: Rl <addr-see-the-website at aetey.se>
> Date: Wed, 1 Feb 2017 19:44:40 +0100
> Subject: [PATCH] Efficiently support several output pixel formats in Cinepak
>  decoder
> 
> Optimize decoding in general and largely improve speed,
> among others by the ability to produce device-native pixel formats.
> 
> The output format can be chosen at runtime by an option.
> 

I can see how conversion in the decoder could speed it up somewhat
(especially if limited by memory bandwidth, I guess), but it's not
really how we do things in FFmpeg. Normally, conversion is done by
libswscale (or whatever the API user uses).

> The format can be also chosen by setting environment variable
> CINEPAK_OUTPUT_FORMAT_OVERRIDE, if this is enabled at build time.

It's not an environment variable, but a preprocessor variable. It's
also not defined by default, which effectively makes some of your
additions dead code. This is also not a thing we do in FFmpeg, and it
usually creates maintenance nightmares. (For you too. Who would care
about accidentally breaking this code when making changes to live parts
of the decoder?)

>  [...]
> -typedef struct CinepakContext {
> +typedef struct CinepakContext CinepakContext;
> +struct CinepakContext {
> +    const AVClass *class;
>  
>      AVCodecContext *avctx;
>      AVFrame *frame;
> @@ -71,24 +87,111 @@ typedef struct CinepakContext {
>      int sega_film_skip_bytes;
>  
>      uint32_t pal[256];
> -} CinepakContext;

Stray change?

>  
> -static void cinepak_decode_codebook (cvid_codebook *codebook,
> -                                     int chunk_id, int size, const uint8_t *data)
> +    void (*decode_codebook)(CinepakContext *s,
> +                            cvid_codebook *codebook, int chunk_id,
> +                            int size, const uint8_t *data);
> +    int  (*decode_vectors)(CinepakContext *s, cvid_strip *strip,
> +                           int chunk_id, int size, const uint8_t *data);
> +/* options */
> +    enum AVPixelFormat out_pixfmt;
> +};
> +
> +#define OFFSET(x) offsetof(CinepakContext, x)
> +#define VD AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM
> +static const AVOption options[] = {
> +{"output_pixel_format", "set output pixel format: rgb24/rgb32/rgb565/yuv420p/pal8; yuv420p is approximate", OFFSET(out_pixfmt), AV_OPT_TYPE_PIXEL_FMT, {.i64=AV_PIX_FMT_RGB24}, -1, INT_MAX, VD },
> +    { NULL },
> +};

This is also not how we do things in FFmpeg. Usually get_format is used
when a decoder really can output multiple formats (which is very
rarely, usually for hardware decoding).


> +static void cinepak_decode_codebook_rgb32 (CinepakContext *s,
> +    cvid_codebook *codebook, int chunk_id, int size, const uint8_t *data)


> +static void cinepak_decode_codebook_rgb24 (CinepakContext *s,
> +    cvid_codebook *codebook, int chunk_id, int size, const uint8_t *data)

> +static void cinepak_decode_codebook_rgb565 (CinepakContext *s,
> +    cvid_codebook *codebook, int chunk_id, int size, const uint8_t *data)


So a part of the decoder is essentially duplicated for each format?

> +/* a simplistic version to begin with, it is also fast -- rl */
> +static void cinepak_decode_codebook_yuv420 (CinepakContext *s,
> +    cvid_codebook *codebook, int chunk_id, int size, const uint8_t *data)
> +{
> +    const uint8_t *eod = (data + size);
> +    uint32_t flag, mask;
> +    int      i, n;
> +    uint8_t *p;
> +    int palette_video = s->palette_video;
> +    int selective_update = (chunk_id & 0x01);
> +
> +    /* check if this chunk contains 4- or 6-element vectors */
> +    n    = (chunk_id & 0x04) ? 4 : 6;
> +    flag = 0;
> +    mask = 0;
> +
> +    p = codebook->yuv420[0];
> +    for (i=0; i < 256; i++) {
> +        if (selective_update && !(mask >>= 1)) {
> +            if ((data + 4) > eod)
> +                break;
> +
> +            flag  = AV_RB32 (data);
> +            data += 4;
> +            mask  = 0x80000000;
> +        }
> +
> +        if (!selective_update || (flag & mask)) {
> +            int k;
> +
> +            if ((data + n) > eod)
> +                break;
> +
> +            if (n == 4)
> +                if (palette_video) {
> +/* here we have kind of "more" data than the output format can express */
> +                    int r, g, b, u = 0, v = 0;
> +                    for (k = 0; k < 4; ++k) {
> +                        uint32_t rr = s->pal[*data++];
> +                        r = (rr>>16)&0xff;
> +                        g = (rr>>8) &0xff;
> +                        b =  rr     &0xff;
> +/* calculate the components (https://en.wikipedia.org/wiki/YUV) */
> +                        *p++ = ((r*66+g*129+b*25+128)>>8)+16;
> +                        u += (-r*38-g*74+b*112+128)>>8;
> +                        v += (r*112-g*94-b*18+128)>>8;
> +                    }
> +                    *p++ = (u+2)/4+128;
> +                    *p++ = (v+2)/4+128;
> +                } else { /* grayscale, easy */
> +                    for (k = 0; k < 4; ++k) {
> +                        *p++ = *data++;
> +                    }
> +                    *p++ = 128;
> +                    *p++ = 128;
> +                }
> +            else { /* n == 6 */
> +/* here we'd have to handle double format conversion
> + * Cinepak=>rgb24 and then rgb24=>yuv420p, which can not be shortcut;
> + * for the moment just copying as-is, for simplicity and speed,
> + * color will be slightly off but not much */
> +                *p++ = *data++;
> +                *p++ = *data++;
> +                *p++ = *data++;
> +                *p++ = *data++;
> +                *p++ = *data++ + 128;
> +                *p++ = *data++ + 128;
> +            }
> +        } else {
> +            p += 6;
> +        }
> +    }
> +}

What we _especially_ don't do in FFmpeg is hardcoding colorspace
conversion coefficients in decoders, and doing colorspace conversion in
decoders.

(OK, we do for subtitle formats - which is actually a bad thing, but
historical mistakes.)

> +#ifdef CINEPAK_OUTPUT_FORMAT_OVERRIDE
> +    char *out_fmt_override = getenv("CINEPAK_OUTPUT_FORMAT_OVERRIDE");
> +#endif

Absolutely not acceptable.

> +    switch (avctx->pix_fmt) {
> +    case AV_PIX_FMT_RGB32:
> +        av_log(avctx, AV_LOG_INFO, "Codec output pixel format is rgb32\n");
> +        s->decode_codebook = cinepak_decode_codebook_rgb32;
> +        s->decode_vectors  = cinepak_decode_vectors_rgb32;
> +        break;

AFAIK we don't usually do INFO messages in decoders or anywhere outside
of CLI tools. I'm probably wrong, but it should be avoided anyway.



More information about the ffmpeg-devel mailing list