[FFmpeg-devel] [PATCH 1/2] Bink version 'b' video decoder

Kostya kostya.shishkov
Wed Feb 9 13:17:50 CET 2011


On Wed, Feb 09, 2011 at 11:04:02PM +1100, Peter Ross wrote:
> Based on original patch by Kostya Shishkov

... which is based on original work of Peter Ross :)

> ---
>  libavcodec/bink.c     |  340 ++++++++++++++++++++++++++++++++++++++++++++++---
>  libavcodec/binkdata.h |   41 ++++++
>  2 files changed, 364 insertions(+), 17 deletions(-)
> 
> diff --git a/libavcodec/bink.c b/libavcodec/bink.c
> index 64a10b7..32330fe 100644
> --- a/libavcodec/bink.c
> +++ b/libavcodec/bink.c
> @@ -1,6 +1,7 @@
>  /*
>   * Bink video decoder
>   * Copyright (c) 2009 Konstantin Shishkov
> + * Copyright (C) 2011 Peter Ross <pross at xvid.org>
>   *
>   * This file is part of FFmpeg.
>   *
> @@ -34,6 +35,35 @@
>  static VLC bink_trees[16];
>  
>  /**
> + * IDs for different data types used in old version of Bink video codec
> + */
> +enum OldSources {
> +    BINKB_SRC_BLOCK_TYPES = 0, ///< 8x8 block types
> +    BINKB_SRC_COLORS,          ///< pixel values used for different block types
> +    BINKB_SRC_PATTERN,         ///< 8-bit values for 2-colour pattern fill
> +    BINKB_SRC_X_OFF,           ///< X components of motion value
> +    BINKB_SRC_Y_OFF,           ///< Y components of motion value
> +    BINKB_SRC_INTRA_DC,        ///< DC values for intrablocks with DCT
> +    BINKB_SRC_INTER_DC,        ///< DC values for interblocks with DCT
> +    BINKB_SRC_INTRA_Q,         ///< quantizer values for intrablocks with DCT
> +    BINKB_SRC_INTER_Q,         ///< quantizer values for interblocks with DCT
> +    BINKB_SRC_INTER_COEFS,     ///< number of coefficients for residue blocks
> +
> +    BINKB_NB_SRC
> +};
> +
> +static const int binkb_bundle_sizes[BINKB_NB_SRC] = {
> +    4, 8, 8, 5, 5, 11, 11, 4, 4, 7
> +};
> +
> +static const int binkb_bundle_signed[BINKB_NB_SRC] = {
> +    0, 0, 0, 1, 1, 0, 1, 0, 0, 0
> +};
> +
> +static uint32_t binkb_intra_quant[16][64];
> +static uint32_t binkb_inter_quant[16][64];
> +
> +/**
>   * IDs for different data types used in Bink video codec
>   */
>  enum Sources {
> @@ -85,7 +115,7 @@ typedef struct BinkContext {
>      int            swap_planes;
>      ScanTable      scantable;            ///< permutated scantable for DCT coeffs decoding
>  
> -    Bundle         bundle[BINK_NB_SRC];  ///< bundles for decoding all data types
> +    Bundle         bundle[BINKB_NB_SRC]; ///< bundles for decoding all data types
>      Tree           col_high[16];         ///< trees for decoding high nibble in "colours" data type
>      int            col_lastval;          ///< value of last decoded high nibble in "colours" data type
>  } BinkContext;
> @@ -145,7 +175,7 @@ static av_cold void init_bundles(BinkContext *c)
>      bh = (c->avctx->height + 7) >> 3;
>      blocks = bw * bh;
>  
> -    for (i = 0; i < BINK_NB_SRC; i++) {
> +    for (i = 0; i < BINKB_NB_SRC; i++) {
>          c->bundle[i].data = av_malloc(blocks * 64);
>          c->bundle[i].data_end = c->bundle[i].data + blocks * 64;
>      }
> @@ -159,7 +189,7 @@ static av_cold void init_bundles(BinkContext *c)
>  static av_cold void free_bundles(BinkContext *c)
>  {
>      int i;
> -    for (i = 0; i < BINK_NB_SRC; i++)
> +    for (i = 0; i < BINKB_NB_SRC; i++)
>          av_freep(&c->bundle[i].data);
>  }
>  
> @@ -481,17 +511,84 @@ static inline int get_value(BinkContext *c, int bundle)
>      return ret;
>  }
>  
> +static void binkb_init_bundle(BinkContext *c, int bundle_num)
> +{
> +    c->bundle[bundle_num].cur_dec =
> +    c->bundle[bundle_num].cur_ptr = c->bundle[bundle_num].data;
> +    c->bundle[bundle_num].len = 13;
> +}
> +
> +static void binkb_init_bundles(BinkContext *c)
> +{
> +    int i;
> +    for (i = 0; i < BINKB_NB_SRC; i++)
> +        binkb_init_bundle(c, i);
> +}
> +
> +static int binkb_read_bundle(BinkContext *c, GetBitContext *gb, int bundle_num)
> +{
> +    const int bits = binkb_bundle_sizes[bundle_num];
> +    const int mask = 1 << (bits - 1);
> +    const int issigned = binkb_bundle_signed[bundle_num];
> +    Bundle *b = &c->bundle[bundle_num];
> +    int i, len;
> +
> +    CHECK_READ_VAL(gb, b, len);
> +    if (bits <= 8) {
> +        if (!issigned) {
> +            for (i = 0; i < len; i++)
> +                *b->cur_dec++ = get_bits(gb, bits);
> +        } else {
> +            for (i = 0; i < len; i++)
> +                *b->cur_dec++ = get_bits(gb, bits) - mask;
> +        }
> +    } else {
> +        int16_t *dst = (int16_t*)b->cur_dec;
> +
> +        if (!issigned) {
> +            for (i = 0; i < len; i++)
> +                *dst++ = get_bits(gb, bits);
> +        } else {
> +            for (i = 0; i < len; i++)
> +                *dst++ = get_bits(gb, bits) - mask;
> +        }
> +        b->cur_dec = (uint8_t*)dst;
> +    }
> +    return 0;
> +}
> +
> +static inline int binkb_get_value(BinkContext *c, int bundle_num)
> +{
> +    int16_t ret;
> +    const int bits = binkb_bundle_sizes[bundle_num];
> +
> +    if (bits <= 8) {
> +        int val = *c->bundle[bundle_num].cur_ptr++;
> +        return binkb_bundle_signed[bundle_num] ? (int8_t)val : val;
> +    }
> +    ret = *(int16_t*)c->bundle[bundle_num].cur_ptr;
> +    c->bundle[bundle_num].cur_ptr += 2;
> +    return ret;
> +}

I'd use simple "int ret" here - the only place where you get int16_t it's
explicitly cast anyway.

> +typedef const uint32_t matrices[16][64];
> +static const matrices * matrix_selector[2][2] = {
> +    { &bink_inter_quant,  &bink_intra_quant  },
> +    { &binkb_inter_quant, &binkb_intra_quant },
> +};
> +

quant_matrices and bink_quant_matrices correspondingly would be a better
name IMO

>  /**
>   * Read 8x8 block of DCT coefficients.
>   *
>   * @param gb       context for reading bits
>   * @param block    place for storing coefficients
>   * @param scan     scan order table
> + * @param is_binkb use version 'b' quantizer matrices
>   * @param is_intra tells what set of quantizer matrices to use
>   * @return 0 for success, negative value in other cases
>   */
>  static int read_dct_coeffs(GetBitContext *gb, DCTELEM block[64], const uint8_t *scan,
> -                           int is_intra)
> +                           int is_binkb, int is_intra, int q)
>  {
>      int coef_list[128];
>      int mode_list[128];
> @@ -571,9 +668,14 @@ static int read_dct_coeffs(GetBitContext *gb, DCTELEM block[64], const uint8_t *
>          }
>      }
>  
> -    quant_idx = get_bits(gb, 4);
> -    quant = is_intra ? bink_intra_quant[quant_idx]
> -                     : bink_inter_quant[quant_idx];
> +    if (q == -1) {
> +        quant_idx = get_bits(gb, 4);
> +    } else {
> +        quant_idx = q;
> +    }
> +
> +    quant = (*matrix_selector[is_binkb][is_intra])[quant_idx];
> +
>      block[0] = (block[0] * quant[0]) >> 11;
>      for (i = 0; i < coef_count; i++) {
>          int idx = coef_idx[i];
> @@ -673,6 +775,162 @@ static int read_residue(GetBitContext *gb, DCTELEM block[64], int masks_count)
>      return 0;
>  }
>  
> +/**
> + * Copy 8x8 block from src to destination; permit src and dst to be overlapped
> + */
> +static inline void put_pixels8x8_overlapped(DSPContext *s, uint8_t *dst, uint8_t *src, int stride)
> +{
> +    if (src + 8*stride < dst || src >= dst + 8*stride) {
> +        s->put_pixels_tab[1][0](dst, src, stride, 8);
> +    } else {
> +        uint8_t tmp[64];
> +        int i;
> +        for (i = 0; i < 8; i++)
> +            memcpy(tmp + i*8, src + i*stride, 8);
> +        for (i = 0; i < 8; i++)
> +            memcpy(dst + i*stride, tmp + i*8, 8);
> +    }
> +}

I think it's better if you use than condition directly in code, so this function
is invoked only on overlapped area, that would make it theoretically DSPUtilisable
if somebody has such idea.

> +static int binkb_decode_plane(BinkContext *c, GetBitContext *gb, int plane_idx,
> +                              int is_key, int is_chroma)
> +{
> +    int blk;
> +    int i, j, bx, by;
> +    uint8_t *dst, *ref, *ref_start, *ref_end;
> +    int v, col[2];
> +    const uint8_t *scan;
> +    int xoff, yoff;
> +    DECLARE_ALIGNED(16, DCTELEM, block[64]);
> +    int coordmap[64];
> +    int ybias = is_key ? -15 : 0;
> +    int qp;
> +
> +    const int stride = c->pic.linesize[plane_idx];
> +    int bw = is_chroma ? (c->avctx->width  + 15) >> 4 : (c->avctx->width  + 7) >> 3;
> +    int bh = is_chroma ? (c->avctx->height + 15) >> 4 : (c->avctx->height + 7) >> 3;
> +
> +    binkb_init_bundles(c);
> +    ref_start = c->pic.data[plane_idx];
> +    ref_end   = c->pic.data[plane_idx] + (bh * c->pic.linesize[plane_idx] + bw) * 8;
> +
> +    for (i = 0; i < 64; i++)
> +        coordmap[i] = (i & 7) + (i >> 3) * stride;
> +
> +    for (by = 0; by < bh; by++) {
> +        for (i = 0; i < BINKB_NB_SRC; i++) {
> +            if (binkb_read_bundle(c, gb, i) < 0)
> +                return -1;
> +        }
> +
> +        dst  = c->pic.data[plane_idx]  + 8*by*stride;
> +        for (bx = 0; bx < bw; bx++, dst += 8) {
> +            blk = binkb_get_value(c, BINKB_SRC_BLOCK_TYPES);
> +            switch (blk) {
> +            case 0:
> +                break;
> +            case 1:
> +                scan = bink_patterns[get_bits(gb, 4)];
> +                i = 0;
> +                do {
> +                    int mode, run;
> +
> +                    mode = get_bits1(gb);
> +                    run = get_bits(gb, binkb_runbits[i]) + 1;
> +
> +                    i += run;
> +                    if (i > 64) {
> +                        av_log(c->avctx, AV_LOG_ERROR, "Run went out of bounds\n");
> +                        return -1;
> +                    }
> +                    if (mode) {
> +                        v = binkb_get_value(c, BINKB_SRC_COLORS);
> +                        for (j = 0; j < run; j++)
> +                            dst[coordmap[*scan++]] = v;
> +                    } else {
> +                        for (j = 0; j < run; j++)
> +                            dst[coordmap[*scan++]] = binkb_get_value(c, BINKB_SRC_COLORS);
> +                    }
> +                } while (i < 63);
> +                if (i == 63)
> +                    dst[coordmap[*scan++]] = binkb_get_value(c, BINKB_SRC_COLORS);
> +                break;
> +            case 2:
> +                c->dsp.clear_block(block);
> +                block[0] = binkb_get_value(c, BINKB_SRC_INTRA_DC);
> +                qp = binkb_get_value(c, BINKB_SRC_INTRA_Q);
> +                read_dct_coeffs(gb, block, c->scantable.permutated, 1, 1, qp);
> +                c->dsp.idct_put(dst, stride, block);
> +                break;
> +            case 3:
> +                xoff = binkb_get_value(c, BINKB_SRC_X_OFF);
> +                yoff = binkb_get_value(c, BINKB_SRC_Y_OFF) + ybias;
> +                ref = dst + xoff + yoff * stride;
> +                if (ref < ref_start || ref + 8*stride > ref_end) {
> +                    av_log(c->avctx, AV_LOG_WARNING, "Reference block out of bounds\n");

Here and later it should be "Reference block is out of bounds"

> +                } else {
> +                    put_pixels8x8_overlapped(&c->dsp, dst, ref, stride);
> +                }
> +                c->dsp.clear_block(block);
> +                v = binkb_get_value(c, BINKB_SRC_INTER_COEFS);
> +                read_residue(gb, block, v);
> +                c->dsp.add_pixels8(dst, block, stride);
> +                break;
> +            case 4:
> +                xoff = binkb_get_value(c, BINKB_SRC_X_OFF);
> +                yoff = binkb_get_value(c, BINKB_SRC_Y_OFF) + ybias;
> +                ref = dst + xoff + yoff * stride;
> +                if (ref < ref_start || ref + 8 * stride > ref_end) {
> +                    av_log(c->avctx, AV_LOG_WARNING, "Reference block out of bounds\n");
> +                } else {
> +                    put_pixels8x8_overlapped(&c->dsp, dst, ref, stride);
> +                }
> +                c->dsp.clear_block(block);
> +                block[0] = binkb_get_value(c, BINKB_SRC_INTER_DC);
> +                qp = binkb_get_value(c, BINKB_SRC_INTER_Q);
> +                read_dct_coeffs(gb, block, c->scantable.permutated, 1, 0, qp);
> +                c->dsp.idct_add(dst, stride, block);
> +                break;
> +            case 5:
> +                v = binkb_get_value(c, BINKB_SRC_COLORS);
> +                c->dsp.fill_block_tab[1](dst, v, stride, 8);
> +                break;
> +            case 6:
> +                for (i = 0; i < 2; i++)
> +                    col[i] = binkb_get_value(c, BINKB_SRC_COLORS);
> +                for (i = 0; i < 8; i++) {
> +                    v = binkb_get_value(c, BINKB_SRC_PATTERN);
> +                    for (j = 0; j < 8; j++, v >>= 1)
> +                        dst[i*stride + j] = col[v & 1];
> +                }
> +                break;
> +            case 7:
> +                xoff = binkb_get_value(c, BINKB_SRC_X_OFF);
> +                yoff = binkb_get_value(c, BINKB_SRC_Y_OFF) + ybias;
> +                ref = dst + xoff + yoff * stride;
> +                if (ref < ref_start || ref + 8 * stride > ref_end) {
> +                    av_log(c->avctx, AV_LOG_WARNING, "Reference block out of bounds\n");
> +                } else {
> +                    put_pixels8x8_overlapped(&c->dsp, dst, ref, stride);
> +                }
> +                break;
> +            case 8:
> +                for (i = 0; i < 8; i++)
> +                    memcpy(dst + i*stride, c->bundle[BINKB_SRC_COLORS].cur_ptr + i*8, 8);
> +                c->bundle[BINKB_SRC_COLORS].cur_ptr += 64;
> +                break;
> +            default:
> +                av_log(c->avctx, AV_LOG_ERROR, "Unknown block type %d\n", blk);
> +                return -1;
> +            }
> +        }
> +    }
> +    if (get_bits_count(gb) & 0x1F) //next plane data starts at 32-bit boundary
> +        skip_bits_long(gb, 32 - (get_bits_count(gb) & 0x1F));
> +
> +    return 0;
> +}
> +
>  static int bink_decode_plane(BinkContext *c, GetBitContext *gb, int plane_idx,
>                               int is_chroma)
>  {
> @@ -768,7 +1026,7 @@ static int bink_decode_plane(BinkContext *c, GetBitContext *gb, int plane_idx,
>                  case INTRA_BLOCK:
>                      c->dsp.clear_block(block);
>                      block[0] = get_value(c, BINK_SRC_INTRA_DC);
> -                    read_dct_coeffs(gb, block, c->scantable.permutated, 1);
> +                    read_dct_coeffs(gb, block, c->scantable.permutated, 0, 1, -1);
>                      c->dsp.idct(block);
>                      c->dsp.put_pixels_nonclamped(block, ublock, 8);
>                      break;
> @@ -852,7 +1110,7 @@ static int bink_decode_plane(BinkContext *c, GetBitContext *gb, int plane_idx,
>              case INTRA_BLOCK:
>                  c->dsp.clear_block(block);
>                  block[0] = get_value(c, BINK_SRC_INTRA_DC);
> -                read_dct_coeffs(gb, block, c->scantable.permutated, 1);
> +                read_dct_coeffs(gb, block, c->scantable.permutated, 0, 1, -1);
>                  c->dsp.idct_put(dst, stride, block);
>                  break;
>              case FILL_BLOCK:
> @@ -866,7 +1124,7 @@ static int bink_decode_plane(BinkContext *c, GetBitContext *gb, int plane_idx,
>                  c->dsp.put_pixels_tab[1][0](dst, ref, stride, 8);
>                  c->dsp.clear_block(block);
>                  block[0] = get_value(c, BINK_SRC_INTER_DC);
> -                read_dct_coeffs(gb, block, c->scantable.permutated, 0);
> +                read_dct_coeffs(gb, block, c->scantable.permutated, 0, 0, -1);
>                  c->dsp.idct_add(dst, stride, block);
>                  break;
>              case PATTERN_BLOCK:
> @@ -902,6 +1160,7 @@ static int decode_frame(AVCodecContext *avctx, void *data, int *data_size, AVPac
>      int plane, plane_idx;
>      int bits_count = pkt->size << 3;
>  
> +    if (c->version > 'b') {
>      if(c->pic.data[0])
>          avctx->release_buffer(avctx, &c->pic);
>  
> @@ -909,6 +1168,12 @@ static int decode_frame(AVCodecContext *avctx, void *data, int *data_size, AVPac
>          av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n");
>          return -1;
>      }
> +    } else {
> +        if(avctx->reget_buffer(avctx, &c->pic) < 0){
> +            av_log(avctx, AV_LOG_ERROR, "reget_buffer() failed\n");
> +            return -1;
> +        }
> +    }
>  
>      init_get_bits(&gb, pkt->data, bits_count);
>      if (c->has_alpha) {
> @@ -923,8 +1188,13 @@ static int decode_frame(AVCodecContext *avctx, void *data, int *data_size, AVPac
>      for (plane = 0; plane < 3; plane++) {
>          plane_idx = (!plane || !c->swap_planes) ? plane : (plane ^ 3);
>  
> -        if (bink_decode_plane(c, &gb, plane_idx, !!plane) < 0)
> -            return -1;
> +        if (c->version > 'b') {
> +            if (bink_decode_plane(c, &gb, plane_idx, !!plane) < 0)
> +                return -1;
> +        } else {
> +            if (binkb_decode_plane(c, &gb, plane_idx, !pkt->pts, !!plane) < 0)
> +                return -1;
> +        }
>          if (get_bits_count(&gb) >= bits_count)
>              break;
>      }
> @@ -933,24 +1203,53 @@ static int decode_frame(AVCodecContext *avctx, void *data, int *data_size, AVPac
>      *data_size = sizeof(AVFrame);
>      *(AVFrame*)data = c->pic;
>  
> -    FFSWAP(AVFrame, c->pic, c->last);
> +    if (c->version > 'b')
> +        FFSWAP(AVFrame, c->pic, c->last);
>  
>      /* always report that the buffer was completely consumed */
>      return pkt->size;
>  }
>  
> +/**
> + * Caclulate quantization tables for version b
> + */
> +static av_cold void binkb_calc_quant()
> +{
> +    float s[64];
> +    int i, j;
> +
> +    for (j = 0; j < 8; j++) {
> +        for (i = 0; i < 8; i++) {
> +            if (j && j != 4)
> +               if (i && i != 4)
> +                   s[j*8 + i] = cos(j * M_PI/16.0f) * cos(i * M_PI/16.0f) * 2.0f;
> +               else
> +                   s[j*8 + i] = cos(j * M_PI/16.0f) * sqrt(2.0f);
> +            else
> +               if (i && i != 4)
> +                   s[j*8 + i] = cos(i * M_PI/16.0f) * sqrt(2.0f);
> +               else
> +                   s[j*8 + i] = 1.0f;
> +        }
> +    }
> +
> +    for (j = 0; j < 16; j++) {
> +        for (i = 0; i < 64; i++) {
> +            binkb_intra_quant[j][i] = (1L<<12) * binkb_intra_seed[i] * binkb_num[j]/(float)binkb_den[j] * s[i];
> +            binkb_inter_quant[j][i] = (1L<<12) * binkb_inter_seed[i] * binkb_num[j]/(float)binkb_den[j] * s[i];
> +        }
> +    }
> +}

break the lines, that's too long

>  static av_cold int decode_init(AVCodecContext *avctx)
>  {
>      BinkContext * const c = avctx->priv_data;
>      static VLC_TYPE table[16 * 128][2];
> +    static int binkb_initialised = 0;
>      int i;
>      int flags;
>  
>      c->version = avctx->codec_tag >> 24;
> -    if (c->version < 'c') {
> -        av_log(avctx, AV_LOG_ERROR, "Too old version '%c'\n", c->version);
> -        return -1;
> -    }
>      if (avctx->extradata_size < 4) {
>          av_log(avctx, AV_LOG_ERROR, "Extradata missing or too short\n");
>          return -1;
> @@ -984,6 +1283,13 @@ static av_cold int decode_init(AVCodecContext *avctx)
>  
>      init_bundles(c);
>  
> +    if (c->version == 'b') {
> +        if (!binkb_initialised) {
> +            binkb_calc_quant();
> +            binkb_initialised = 1;
> +        }
> +    }
> +
>      return 0;
>  }
>  
> diff --git a/libavcodec/binkdata.h b/libavcodec/binkdata.h
> index 1ca34a6..2c20b4a 100644
> --- a/libavcodec/binkdata.h
> +++ b/libavcodec/binkdata.h
> @@ -611,4 +611,45 @@ static const uint32_t bink_inter_quant[16][64] = {
>  },
>  };
>  
> +static const uint8_t binkb_runbits[64] = {
> +    6, 6, 6, 6, 6, 6, 6, 6,
> +    6, 6, 6, 6, 6, 6, 6, 6,
> +    6, 6, 6, 6, 6, 6, 6, 6,
> +    6, 6, 6, 6, 6, 6, 6, 6,
> +    5, 5, 5, 5, 5, 5, 5, 5,
> +    5, 5, 5, 5, 5, 5, 5, 5,
> +    4, 4, 4, 4, 4, 4, 4, 4,
> +    3, 3, 3, 3, 2, 2, 1, 0,
> +};
> +
> +static const uint8_t binkb_intra_seed[64] = {
> +    16, 16, 16, 19, 16, 19, 22, 22,
> +    22, 22, 26, 24, 26, 22, 22, 27,
> +    27, 27, 26, 26, 26, 29, 29, 29,
> +    27, 27, 27, 26, 34, 34, 34, 29,
> +    29, 29, 27, 27, 37, 34, 34, 32,
> +    32, 29, 29, 38, 37, 35, 35, 34,
> +    35, 40, 40, 40, 38, 38, 48, 48,
> +    46, 46, 58, 56, 56, 69, 69, 83,
> +};
> +
> +static const uint8_t binkb_inter_seed[64] = {
> +    16, 17, 17, 18, 18, 18, 19, 19,
> +    19, 19, 20, 20, 20, 20, 20, 21,
> +    21, 21, 21, 21, 21, 22, 22, 22,
> +    22, 22, 22, 22, 23, 23, 23, 23,
> +    23, 23, 23, 23, 24, 24, 24, 25,
> +    24, 24, 24, 25, 26, 26, 26, 26,
> +    25, 27, 27, 27, 27, 27, 28, 28,
> +    28, 28, 30, 30, 30, 31, 31, 33,
> +};
> +
> +static const uint8_t binkb_num[16] = {
> +    1, 4, 5, 2, 7, 8, 3, 7, 4, 9, 5, 6, 7, 8, 9, 10
> +};
> +
> +static const uint8_t binkb_den[16] = {
> +    1, 3, 3, 1, 3, 3, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1
> +};
> +
>  #endif /* AVCODEC_BINKDATA_H */
> -- 
> 1.7.1
> 
> 
> -- Peter

Overall - excellent, please RE more codecs :)



More information about the ffmpeg-devel mailing list