[FFmpeg-devel] [PATCH] encoder for adobe's flash ScreenVideo2 codec

Joshua Warner joshuawarner32
Tue Jul 21 16:57:15 CEST 2009


Hi all,

I've developed an encoder for Adobe's Flash ScreenVideo2 format, which is
stored in flv files.  My encoder currently only supports a large subset of
the format.  The only player that supports this codec (so far) is Adobe
Flash Player itself, but ScreenVideo2 makes dramatic improvement in file
size over ScreenVideo (currently in ffmpeg as flashsv) - and should be very
useful for uploading screencasts, etc.  Most options (block size, etc) now
just fall back on defaults because I couldn't find a general algorithm that
produced consistantly better results than these.  All the code is in place
to be able to change these parameters dynamically, so future improvements
there should be easy.  The patch is attached.

The codec is listed as flashsv2 in ffmpeg.

Joshua Warner
-------------- next part --------------
Index: Changelog
===================================================================
--- Changelog	(revision 19479)
+++ Changelog	(working copy)
@@ -191,6 +191,7 @@
 - Gopher client support
 - MXF D-10 muxer
 - generic metadata API
+- flash ScreenVideo2 encoder
 
 
 
Index: libavcodec/allcodecs.c
===================================================================
--- libavcodec/allcodecs.c	(revision 19479)
+++ libavcodec/allcodecs.c	(working copy)
@@ -92,6 +92,7 @@
     REGISTER_ENCDEC  (FFV1, ffv1);
     REGISTER_ENCDEC  (FFVHUFF, ffvhuff);
     REGISTER_ENCDEC  (FLASHSV, flashsv);
+    REGISTER_ENCODER (FLASHSV2, flashsv2);
     REGISTER_DECODER (FLIC, flic);
     REGISTER_ENCDEC  (FLV, flv);
     REGISTER_DECODER (FOURXM, fourxm);
Index: libavcodec/avcodec.h
===================================================================
--- libavcodec/avcodec.h	(revision 19479)
+++ libavcodec/avcodec.h	(working copy)
@@ -30,7 +30,7 @@
 #include "libavutil/avutil.h"
 
 #define LIBAVCODEC_VERSION_MAJOR 52
-#define LIBAVCODEC_VERSION_MINOR 32
+#define LIBAVCODEC_VERSION_MINOR 33
 #define LIBAVCODEC_VERSION_MICRO  0
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
@@ -198,6 +198,7 @@
     CODEC_ID_V210,
     CODEC_ID_DPX,
     CODEC_ID_MAD,
+    CODEC_ID_FLASHSV2,
 
     /* various PCM "codecs" */
     CODEC_ID_PCM_S16LE= 0x10000,
Index: libavcodec/Makefile
===================================================================
--- libavcodec/Makefile	(revision 19479)
+++ libavcodec/Makefile	(working copy)
@@ -92,6 +92,7 @@
 OBJS-$(CONFIG_FLAC_ENCODER)            += flacenc.o flacdata.o flac.o lpc.o
 OBJS-$(CONFIG_FLASHSV_DECODER)         += flashsv.o
 OBJS-$(CONFIG_FLASHSV_ENCODER)         += flashsvenc.o
+OBJS-$(CONFIG_FLASHSV2_ENCODER)         += flashsv2enc.o
 OBJS-$(CONFIG_FLIC_DECODER)            += flicvideo.o
 OBJS-$(CONFIG_FLV_DECODER)             += h263dec.o h263.o mpeg12data.o mpegvideo.o error_resilience.o
 OBJS-$(CONFIG_FLV_ENCODER)             += mpegvideo_enc.o motion_est.o ratecontrol.o h263.o mpeg12data.o mpegvideo.o error_resilience.o
Index: libavcodec/flashsv2enc.c
===================================================================
--- libavcodec/flashsv2enc.c	(revision 0)
+++ libavcodec/flashsv2enc.c	(revision 0)
@@ -0,0 +1,962 @@
+/*
+ * Flash Screen Video Version 2 encoder
+ * Copyright (C) 2009 Joshua Warner
+ *
+ * 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
+ */
+
+/**
+ * @file libavcodec/flashsv2enc.c
+ * Flash Screen Video Version 2 encoder
+ * @author Joshua Warner
+ */
+
+/* Differences from version 1 stream:
+ * NOTE: Currently, the only player that supports version 2 streams is Adobe Flash Player itself.
+ * * Supports sending only a range of scanlines in a block, 
+ *   indicating a difference from the corresponding block in the last keyframe.
+ * * Supports initializing the zlib dictionary with data from the corresponding 
+ *   block in the last keyframe, to improve compression.
+ * * Supports a hybrid 15-bit rgb / 7-bit pallet color space.
+ */
+
+/* TODO:
+ * Don't keep Block structures for both current frame and keyframe.
+ * Make better heuristics for deciding stream parameters (optimum_* functions).  Currently these return constants.
+ * Figure out how to encode pallet information in the stream, choose an optimum pallet at each keyframe.
+ * Figure out how the zlibPrimeCompressCurrent flag works, implement support.
+ * Find other sample files (that weren't generated here), develop a decoder.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <zlib.h>
+
+#include "avcodec.h"
+#include "put_bits.h"
+#include "bytestream.h"
+
+#define HAS_IFRAME_IMAGE 0x02
+#define HAS_PALLET_INFO 0x01
+
+#define COLORSPACE_BGR 0x00
+#define COLORSPACE_15_7 0x10
+#define HAS_DIFF_BLOCKS 0x04
+#define ZLIB_PRIME_COMPRESS_CURRENT 0x02
+#define ZLIB_PRIME_COMPRESS_PREVIOUS 0x01
+
+typedef struct Block {
+    uint8_t *enc;
+    uint8_t *sl_begin, *sl_end;
+    int enc_size;
+    uint8_t *data;
+    int data_size;
+
+    uint8_t start, len;
+    uint8_t dirty;
+    uint8_t col, row, width, height;
+    uint8_t flags;
+} Block;
+
+typedef struct Pallet {
+    unsigned colors[128];
+    uint8_t *index;
+} Pallet;
+
+typedef struct FlashSV2Context {
+    AVCodecContext *avctx;
+    uint8_t *current_frame;
+    uint8_t *key_frame;
+    AVFrame frame;
+    uint8_t *encbuffer;
+    uint8_t *keybuffer;
+    uint8_t *databuffer;
+
+    Block *frame_blocks;
+    Block *key_blocks;
+    int frame_size;
+    int blocks_size;
+
+    int use15_7, dist, comp;
+
+    int rows, cols;
+
+    int last_key_frame;
+
+    int image_width, image_height;
+    int block_width, block_height;
+    uint8_t flags;
+    uint8_t use_custom_pallet;
+    uint8_t pallet_type;        //0=>default, 1=>custom - changed when pallet regenerated.
+    Pallet pallet;
+
+    double tot_blocks;          //blocks encoded since last keyframe
+    double diff_blocks;         //blocks that were different since last keyframe
+    double tot_lines;           //total scanlines in image since last keyframe
+    double diff_lines;          //scanlines that were different since last keyframe
+    double raw_size;            //size of raw frames since last keyframe
+    double comp_size;           //size of compressed data since last keyframe
+    double uncomp_size;         //size of uncompressed data since last keyframe
+
+    double total_bits;          //total bits written to stream so far
+} FlashSV2Context;
+
+static void cleanup(FlashSV2Context * s)
+{
+    if (s->encbuffer)
+        av_free(s->encbuffer);
+    if (s->keybuffer)
+        av_free(s->keybuffer);
+    if (s->databuffer)
+        av_free(s->databuffer);
+    if (s->current_frame)
+        av_free(s->current_frame);
+    if (s->key_frame)
+        av_free(s->key_frame);
+
+    if (s->frame_blocks)
+        av_free(s->frame_blocks);
+    if (s->key_blocks)
+        av_free(s->key_blocks);
+}
+
+static int generate_default_pallet(Pallet * pallet);
+
+static void init_blocks(FlashSV2Context * s, Block * blocks,
+                        uint8_t * encbuf, uint8_t * databuf)
+{
+    int row, col;
+    Block *b;
+    for (col = 0; col < s->cols; col++) {
+        for (row = 0; row < s->rows; row++) {
+            b = blocks + (col + row * s->cols);
+            b->width = (col < s->cols - 1)
+                || (s->image_width % s->block_width ==
+                    0) ? s->block_width : s->image_width % s->block_width;
+            b->height = (row < s->rows - 1)
+                || (s->image_height % s->block_height ==
+                    0) ? s->block_height : s->image_height %
+                s->block_height;
+            b->row = row;
+            b->col = col;
+            b->enc = encbuf;
+            b->data = databuf;
+            encbuf += b->width * b->height * 3;
+            databuf += databuf == 0 ? 0 : b->width * b->height * 6;
+        }
+    }
+}
+
+static void reset_stats(FlashSV2Context * s)
+{
+    s->diff_blocks = 0.1;
+    s->tot_blocks = 1;
+    s->diff_lines = 0.1;
+    s->tot_lines = 1;
+    s->raw_size = s->comp_size = s->uncomp_size = 10;
+}
+
+static av_cold int flashsv2_encode_init(AVCodecContext * avctx)
+{
+    FlashSV2Context *s = avctx->priv_data;
+
+    s->avctx = avctx;
+
+    s->comp = avctx->compression_level;
+    if (s->comp == -1)
+        s->comp = 9;
+    if (s->comp < 0 || s->comp > 9) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Compression level should be 0-9, not %d\n", s->comp);
+        return -1;
+    }
+
+
+    if ((avctx->width > 4095) || (avctx->height > 4095)) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Input dimensions too large, input must be max 4096x4096 !\n");
+        return -1;
+    }
+
+    if (avcodec_check_dimensions(avctx, avctx->width, avctx->height) < 0) {
+        return -1;
+    }
+
+
+    s->last_key_frame = 0;
+
+    s->image_width = avctx->width;
+    s->image_height = avctx->height;
+
+    s->block_width = (s->image_width / 12) & ~15;
+    s->block_height = (s->image_height / 12) & ~15;
+
+    s->rows = (s->image_height + s->block_height - 1) / s->block_height;
+    s->cols = (s->image_width + s->block_width - 1) / s->block_width;
+
+    s->frame_size = s->image_width * s->image_height * 3;
+    s->blocks_size = s->rows * s->cols * sizeof(Block);
+
+    s->encbuffer = av_mallocz(s->frame_size);
+    s->keybuffer = av_mallocz(s->frame_size);
+    s->databuffer = av_mallocz(s->frame_size * 6);
+    s->current_frame = av_mallocz(s->frame_size);
+    s->key_frame = av_mallocz(s->frame_size);
+    s->frame_blocks = av_mallocz(s->blocks_size);
+    s->key_blocks = av_mallocz(s->blocks_size);
+    s->pallet.index = av_mallocz(1 << 15);
+
+    init_blocks(s, s->frame_blocks, s->encbuffer, s->databuffer);
+    init_blocks(s, s->key_blocks, s->keybuffer, 0);
+    reset_stats(s);
+    s->total_bits = 1;
+
+    s->use_custom_pallet = 0;
+    s->pallet_type = -1;        // so that the pallet will be generated in reconfigure_at_keyframe
+
+    if (!s->encbuffer || !s->keybuffer || !s->databuffer
+        || !s->current_frame || !s->key_frame || !s->key_blocks
+        || !s->frame_blocks || !s->pallet.index) {
+        av_log(avctx, AV_LOG_ERROR, "Memory allocation failed.\n");
+        cleanup(s);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int new_key_frame(FlashSV2Context * s)
+{
+    int i;
+    memcpy(s->keybuffer, s->encbuffer, s->frame_size);
+    memcpy(s->key_blocks, s->frame_blocks, s->blocks_size);
+    memcpy(s->key_frame, s->current_frame, s->frame_size);
+
+    for (i = 0; i < s->rows * s->cols; i++) {
+        s->key_blocks[i].enc += (s->keybuffer - s->encbuffer);
+        s->key_blocks[i].sl_begin = s->key_blocks[i].sl_end =
+            s->key_blocks[i].data = 0;
+    }
+
+    return 0;
+}
+
+static int write_pallet(FlashSV2Context * s, uint8_t * buf, int buf_size)
+{
+    //this isn't implemented yet!  Default pallet only!
+    return -1;
+}
+
+static int write_header(FlashSV2Context * s, uint8_t * buf, int buf_size)
+{
+    PutBitContext pb;
+    int buf_pos, len;
+
+    if (buf_size < 5)
+        return -1;
+
+    init_put_bits(&pb, buf, buf_size * 8);
+
+    put_bits(&pb, 4, (s->block_width / 16) - 1);
+    put_bits(&pb, 12, s->image_width);
+    put_bits(&pb, 4, (s->block_height / 16) - 1);
+    put_bits(&pb, 12, s->image_height);
+
+    flush_put_bits(&pb);
+    buf_pos = 4;
+
+    buf[buf_pos++] = s->flags;
+
+    if (s->flags & HAS_PALLET_INFO) {
+        len = write_pallet(s, buf + buf_pos, buf_size - buf_pos);
+        if (len < 0)
+            return -1;
+        buf_pos += len;
+    }
+
+    return buf_pos;
+}
+
+static int write_block(Block * b, uint8_t * buf, int buf_size)
+{
+    int buf_pos = 0;
+    unsigned block_size = b->data_size;
+
+    if (b->flags & HAS_DIFF_BLOCKS)
+        block_size += 2;
+    if (b->flags & ZLIB_PRIME_COMPRESS_CURRENT)
+        block_size += 2;
+    if (block_size > 0)
+        block_size += 1;
+    if (buf_size < block_size + 2)
+        return -1;
+
+    buf[buf_pos++] = block_size >> 8;
+    buf[buf_pos++] = block_size;
+
+    if (block_size == 0)
+        return buf_pos;
+
+    buf[buf_pos++] = b->flags;
+
+    if (b->flags & HAS_DIFF_BLOCKS) {
+        buf[buf_pos++] = (uint8_t) (b->start);
+        buf[buf_pos++] = (uint8_t) (b->len);
+    }
+
+    if (b->flags & ZLIB_PRIME_COMPRESS_CURRENT) {
+        //This feature of the format is poorly understood, and as of now, unused.
+        buf[buf_pos++] = (uint8_t) (b->col);
+        buf[buf_pos++] = (uint8_t) (b->row);
+    }
+
+    memcpy(buf + buf_pos, b->data, b->data_size);
+
+    buf_pos += b->data_size;
+
+    return buf_pos;
+}
+
+static int encode_zlib(Block * b, uint8_t * buf, int *buf_size, int comp)
+{
+    int res =
+        compress2(buf, buf_size, b->sl_begin, b->sl_end - b->sl_begin,
+                  comp);
+    return res == Z_OK ? 0 : -1;
+}
+
+static int encode_zlibprime(Block * b, Block * prime, uint8_t * buf,
+                            int *buf_size, int comp)
+{
+    z_stream s;
+    int res;
+    s.zalloc = NULL;
+    s.zfree = NULL;
+    s.opaque = NULL;
+    res = deflateInit(&s, comp);
+    if (res < 0)
+        return -1;
+
+    s.next_in = prime->enc;
+    s.avail_in = prime->enc_size;
+    while (s.avail_in > 0) {
+        s.next_out = buf;
+        s.avail_out = *buf_size;
+        res = deflate(&s, Z_SYNC_FLUSH);
+        if (res < 0)
+            return -1;
+    }
+
+    s.next_in = b->sl_begin;
+    s.avail_in = b->sl_end - b->sl_begin;
+    s.next_out = buf;
+    s.avail_out = *buf_size;
+    res = deflate(&s, Z_FINISH);
+    deflateEnd(&s);
+    *buf_size -= s.avail_out;
+    if (res != Z_STREAM_END)
+        return -1;
+    return 0;
+}
+
+static int encode_bgr(Block * b, uint8_t * src, int stride)
+{
+    int i;
+    uint8_t *ptr = b->enc;
+    for (i = 0; i < b->start; i++) {
+        memcpy(ptr + i * b->width * 3, src + i * stride, b->width * 3);
+    }
+    b->sl_begin = ptr + i * b->width * 3;
+    for (; i < b->start + b->len; i++) {
+        memcpy(ptr + i * b->width * 3, src + i * stride, b->width * 3);
+    }
+    b->sl_end = ptr + i * b->width * 3;
+    for (; i < b->height; i++) {
+        memcpy(ptr + i * b->width * 3, src + i * stride, b->width * 3);
+    }
+    b->enc_size = ptr + i * b->width * 3 - b->enc;
+    return b->enc_size;
+}
+
+static inline unsigned pixel_color15(uint8_t * src)
+{
+    return (src[0] >> 3) | ((src[1] & 0xf8) << 2) | ((src[2] & 0xf8) << 7);
+}
+
+static inline unsigned int chroma_diff(unsigned int c1, unsigned int c2)
+{
+    unsigned int t1 =
+        (c1 & 0x000000ff) + ((c1 & 0x0000ff00) >> 8) +
+        ((c1 & 0x00ff0000) >> 16);
+    unsigned int t2 =
+        (c2 & 0x000000ff) + ((c2 & 0x0000ff00) >> 8) +
+        ((c2 & 0x00ff0000) >> 16);
+
+    return abs(t1 - t2) + abs((c1 & 0x000000ff) - (c2 & 0x000000ff)) +
+        abs(((c1 & 0x0000ff00) >> 8) - ((c2 & 0x0000ff00) >> 8))
+        + abs(((c1 & 0x00ff0000) >> 16) - ((c2 & 0x00ff0000) >> 16));
+}
+
+static inline int pixel_color7_fast(Pallet * pallet, unsigned c15)
+{
+    return pallet->index[c15];
+}
+
+static int pixel_color7_slow(Pallet * pallet, unsigned color)
+{
+    int i, min = 0x7fffffff;
+    int minc = -1;
+    for (i = 0; i < 128; i++) {
+        int c1 = pallet->colors[i];
+        int diff = chroma_diff(c1, color);
+        if (diff < min) {
+            min = diff;
+            minc = i;
+        }
+    }
+    return minc;
+}
+
+static inline unsigned pixel_bgr(uint8_t * src)
+{
+    return (src[2]) | (src[1] << 8) | (src[2] << 16);
+}
+
+static int write_pixel_15_7(Pallet * pallet, uint8_t * dest, uint8_t * src,
+                            int dist)
+{
+    unsigned c15 = pixel_color15(src);
+    unsigned color = pixel_bgr(src);
+    int d15 = chroma_diff(color, color & 0x00f8f8f8);
+    int c7 = pixel_color7_fast(pallet, c15);
+    int d7 = chroma_diff(color, pallet->colors[c7]);
+    if (dist + d15 >= d7) {
+        dest[0] = (uint8_t) c7;
+        return 1;
+    } else {
+        dest[0] = 0x80 | (uint8_t) (c15 >> 8);
+        dest[1] = c15 & 0xff;
+        return 2;
+    }
+}
+
+static int update_pallet_index(Pallet * pallet)
+{
+    int r, g, b;
+    unsigned int bgr, c15, index;
+    for (r = 4; r < 256; r += 8) {
+        for (g = 4; g < 256; g += 8) {
+            for (b = 4; b < 256; b += 8) {
+                bgr = b | (g << 8) | (r << 16);
+                c15 = (b >> 3) | ((g & 0xf8) << 2) | ((r & 0xf8) << 7);
+                index = pixel_color7_slow(pallet, bgr);
+
+                pallet->index[c15] = index;
+            }
+        }
+    }
+    return 0;
+}
+
+static const unsigned int default_screen_video_v2_palette[128] = {
+    0x00000000, 0x00333333, 0x00666666, 0x00999999, 0x00CCCCCC, 0x00FFFFFF,
+    0x00330000, 0x00660000, 0x00990000, 0x00CC0000, 0x00FF0000, 0x00003300,
+    0x00006600, 0x00009900, 0x0000CC00, 0x0000FF00, 0x00000033, 0x00000066,
+    0x00000099, 0x000000CC, 0x000000FF, 0x00333300, 0x00666600, 0x00999900,
+    0x00CCCC00, 0x00FFFF00, 0x00003333, 0x00006666, 0x00009999, 0x0000CCCC,
+    0x0000FFFF, 0x00330033, 0x00660066, 0x00990099, 0x00CC00CC, 0x00FF00FF,
+    0x00FFFF33, 0x00FFFF66, 0x00FFFF99, 0x00FFFFCC, 0x00FF33FF, 0x00FF66FF,
+    0x00FF99FF, 0x00FFCCFF, 0x0033FFFF, 0x0066FFFF, 0x0099FFFF, 0x00CCFFFF,
+    0x00CCCC33, 0x00CCCC66, 0x00CCCC99, 0x00CCCCFF, 0x00CC33CC, 0x00CC66CC,
+    0x00CC99CC, 0x00CCFFCC, 0x0033CCCC, 0x0066CCCC, 0x0099CCCC, 0x00FFCCCC,
+    0x00999933, 0x00999966, 0x009999CC, 0x009999FF, 0x00993399, 0x00996699,
+    0x0099CC99, 0x0099FF99, 0x00339999, 0x00669999, 0x00CC9999, 0x00FF9999,
+    0x00666633, 0x00666699, 0x006666CC, 0x006666FF, 0x00663366, 0x00669966,
+    0x0066CC66, 0x0066FF66, 0x00336666, 0x00996666, 0x00CC6666, 0x00FF6666,
+    0x00333366, 0x00333399, 0x003333CC, 0x003333FF, 0x00336633, 0x00339933,
+    0x0033CC33, 0x0033FF33, 0x00663333, 0x00993333, 0x00CC3333, 0x00FF3333,
+    0x00003366, 0x00336600, 0x00660033, 0x00006633, 0x00330066, 0x00663300,
+    0x00336699, 0x00669933, 0x00993366, 0x00339966, 0x00663399, 0x00996633,
+    0x006699CC, 0x0099CC66, 0x00CC6699, 0x0066CC99, 0x009966CC, 0x00CC9966,
+    0x0099CCFF, 0x00CCFF99, 0x00FF99CC, 0x0099FFCC, 0x00CC99FF, 0x00FFCC99,
+    0x00111111, 0x00222222, 0x00444444, 0x00555555, 0x00AAAAAA, 0x00BBBBBB,
+    0x00DDDDDD, 0x00EEEEEE
+};
+
+static int generate_default_pallet(Pallet * pallet)
+{
+    memcpy(pallet->colors, default_screen_video_v2_palette,
+           sizeof(default_screen_video_v2_palette));
+
+    return update_pallet_index(pallet);
+}
+
+static int generate_optimum_pallet(Pallet * pallet, uint8_t * image,
+                                   int width, int height, int stride)
+{
+    //this isn't implemented yet!  Default pallet only!
+    return -1;
+}
+
+static inline int encode_15_7_sl(Pallet * pallet, uint8_t * dest,
+                                 uint8_t * src, int width, int dist)
+{
+    int len = 0, x;
+    for (x = 0; x < width; x++) {
+        len += write_pixel_15_7(pallet, dest + len, src + 3 * x, dist);
+    }
+    return len;
+}
+
+static int encode_15_7(Pallet * pallet, Block * b, uint8_t * src,
+                       int stride, int dist)
+{
+    int i, len;
+    uint8_t *ptr = b->enc;
+    for (i = 0; i < b->start; i++) {
+        len =
+            encode_15_7_sl(pallet, ptr, src + i * stride, b->width, dist);
+        ptr += len;
+    }
+    b->sl_begin = ptr;
+    for (; i < b->start + b->len; i++) {
+        len =
+            encode_15_7_sl(pallet, ptr, src + i * stride, b->width, dist);
+        ptr += len;
+    }
+    b->sl_end = ptr;
+    for (; i < b->height; i++) {
+        len =
+            encode_15_7_sl(pallet, ptr, src + i * stride, b->width, dist);
+        ptr += len;
+    }
+    b->enc_size = ptr - b->enc;
+    return b->enc_size;
+}
+
+static int encode_block(Pallet * pallet, Block * b, Block * prev,
+                        uint8_t * src, int stride, int comp, int dist,
+                        int keyframe)
+{
+    unsigned buf_size = b->width * b->height * 6;
+    uint8_t buf[buf_size];
+    int res;
+    if (b->flags & COLORSPACE_15_7)
+        encode_15_7(pallet, b, src, stride, dist);
+    else
+        encode_bgr(b, src, stride);
+
+    if (b->len > 0) {
+        b->data_size = buf_size;
+        res = encode_zlib(b, b->data, &b->data_size, comp);
+        if (res != 0)
+            return res;
+
+        if (!keyframe) {
+            res = encode_zlibprime(b, prev, buf, &buf_size, comp);
+            if (res != 0)
+                return res;
+
+            if (buf_size < b->data_size) {
+                b->data_size = buf_size;
+                memcpy(b->data, buf, buf_size);
+                b->flags |= ZLIB_PRIME_COMPRESS_PREVIOUS;
+            }
+        }
+    } else
+        b->data_size = 0;
+    return 0;
+}
+
+static int compare_sl(FlashSV2Context * s, Block * b, uint8_t * src,
+                      uint8_t * frame, uint8_t * key, int y, int keyframe)
+{
+    if (memcmp(src, frame, b->width * 3) != 0) {
+        b->dirty = 1;
+        memcpy(frame, src, b->width * 3);
+        s->diff_lines++;
+    }
+    if (memcmp(src, key, b->width * 3) != 0) {
+        if (b->len == 0)
+            b->start = y;
+        b->len = y + 1 - b->start;
+    }
+    return 0;
+}
+
+static int mark_all_blocks(FlashSV2Context * s, uint8_t * src, int stride,
+                           int keyframe)
+{
+    int sl, rsl, col, pos, possl;
+    Block *b;
+    for (sl = s->image_height - 1; sl >= 0; sl--) {
+        for (col = 0; col < s->cols; col++) {
+            rsl = s->image_height - sl - 1;
+            b = s->frame_blocks + col + rsl / s->block_height * s->cols;
+            possl = stride * sl + col * s->block_width * 3;
+            pos = s->image_width * rsl * 3 + col * s->block_width * 3;
+            compare_sl(s, b, src + possl, s->current_frame + pos,
+                       s->key_frame + pos, rsl % s->block_height,
+                       keyframe);
+        }
+    }
+    s->tot_lines += s->image_height * s->cols;
+    return 0;
+}
+
+static int encode_all_blocks(FlashSV2Context * s, int keyframe)
+{
+    int row, col, res;
+    uint8_t *data;
+    Block *b, *prev;
+    for (row = 0; row < s->rows; row++) {
+        for (col = 0; col < s->cols; col++) {
+            b = s->frame_blocks + (row * s->cols + col);
+            prev = s->key_blocks + (row * s->cols + col);
+            if (keyframe) {
+                b->start = 0;
+                b->len = b->height;
+                b->flags = s->use15_7 ? COLORSPACE_15_7 : 0;
+            } else if (!b->dirty) {
+                b->start = 0;
+                b->len = 0;
+                b->data_size = 0;
+                b->flags = s->use15_7 ? COLORSPACE_15_7 : 0;
+                continue;
+            } else {
+                b->flags =
+                    s->use15_7 ? COLORSPACE_15_7 | HAS_DIFF_BLOCKS :
+                    HAS_DIFF_BLOCKS;
+            }
+            if (b->dirty)
+                s->diff_blocks++;
+            data =
+                s->current_frame +
+                s->image_width * 3 * s->block_height * row +
+                s->block_width * col * 3;
+            res =
+                encode_block(&s->pallet, b, prev, data, s->image_width * 3,
+                             s->comp, s->dist, keyframe);
+            s->comp_size += b->data_size;
+            s->uncomp_size += b->enc_size;
+            if (res != 0)
+                return res;
+        }
+    }
+    s->raw_size += s->image_width * s->image_height * 3;
+    s->tot_blocks += s->rows * s->cols;
+    return 0;
+}
+
+static int write_all_blocks(FlashSV2Context * s, uint8_t * buf,
+                            int buf_size)
+{
+    int row, col, buf_pos = 0, len;
+    Block *b;
+    for (row = 0; row < s->rows; row++) {
+        for (col = 0; col < s->cols; col++) {
+            b = s->frame_blocks + row * s->cols + col;
+            len = write_block(b, buf + buf_pos, buf_size - buf_pos);
+            b->start = b->len = b->dirty = 0;
+            if (len < 0)
+                return len;
+            buf_pos += len;
+        }
+    }
+    return buf_pos;
+}
+
+static int write_bitstream(FlashSV2Context * s, uint8_t * src, int stride,
+                           uint8_t * buf, int buf_size, int keyframe)
+{
+    int buf_pos, res;
+
+    res = mark_all_blocks(s, src, stride, keyframe);
+    if (res != 0)
+        return res;
+    res = encode_all_blocks(s, keyframe);
+    if (res != 0)
+        return res;
+
+    res = write_header(s, buf, buf_size);
+    if (res < 0)
+        return res;
+    else
+        buf_pos = res;
+    res = write_all_blocks(s, buf + buf_pos, buf_size - buf_pos);
+    if (res < 0)
+        return res;
+    buf_pos += res;
+    s->total_bits += ((double) buf_pos) * 8.0;
+
+    return buf_pos;
+}
+
+#define FLASHSV2_DUMB
+
+static void recommend_keyframe(FlashSV2Context * s, int *keyframe)
+{
+#ifndef FLASHSV2_DUMB
+    double block_ratio, line_ratio, enc_ratio, comp_ratio, data_ratio;
+    if (s->avctx->gop_size > 0) {
+        block_ratio = s->diff_blocks / s->tot_blocks;
+        line_ratio = s->diff_lines / s->tot_lines;
+        enc_ratio = s->uncomp_size / s->raw_size;
+        comp_ratio = s->comp_size / s->uncomp_size;
+        data_ratio = s->comp_size / s->raw_size;
+
+        if ((block_ratio >= 0.5 && line_ratio / block_ratio <= 0.5)
+            || line_ratio >= 0.95) {
+            *keyframe = 1;
+            return;
+        }
+    }
+#else
+    return;
+#endif
+}
+
+static const double block_size_fraction = 1.0 / 300;
+static int optimum_block_width(FlashSV2Context * s)
+{
+#ifndef FLASHSV2_DUMB
+    //double save = (1-pow(s->diff_lines/s->diff_blocks/s->block_height, 0.5)) * s->comp_size/s->tot_blocks;
+    //double width = block_size_fraction * sqrt(0.5 * save * s->rows * s->cols) * s->image_width;
+    //int pwidth;
+    //av_log(s->avctx, AV_LOG_DEBUG, "block width: %g\n", width);
+    double width;
+    width = ((double) s->image_width) / 10.0;
+    pwidth = ((int) width);
+    pwidth &= ~15;
+    if (pwidth > 256)
+        pwidth = 256;
+    if (pwidth < 16)
+        pwidth = 16;
+    return pwidth;
+#else
+    return 64;
+#endif
+}
+
+static int optimum_block_height(FlashSV2Context * s)
+{
+#ifndef FLASHSV2_DUMB
+    //double save = (1-pow(s->diff_lines/s->diff_blocks/s->block_height, 0.5)) * s->comp_size/s->tot_blocks;
+    //double height = block_size_fraction * sqrt(0.5 * save * s->rows * s->cols) * s->image_height;
+    //int pheight;
+    //av_log(s->avctx, AV_LOG_DEBUG, "block height: %g\n", height);
+    double height;
+    height = ((double) s->image_height) / 10.0;
+    pheight = ((int) height);
+    pheight &= ~15;
+    if (pheight > 256)
+        pheight = 256;
+    if (pheight < 16)
+        pheight = 16;
+    return pheight;
+#else
+    return 64;
+#endif
+}
+
+static const double use15_7_threshold = 8192;
+
+static int optimum_use15_7(FlashSV2Context * s)
+{
+#ifndef FLASHSV2_DUMB
+    double ideal = ((double)
+                    (s->avctx->bit_rate * s->avctx->time_base.den *
+                     s->avctx->ticks_per_frame)) /
+        ((double) s->avctx->time_base.num) * s->avctx->frame_number;
+    av_log(s->avctx, AV_LOG_DEBUG, "s->avctx->bit_rate: %d\n",
+           s->avctx->bit_rate);
+    av_log(s->avctx, AV_LOG_DEBUG, "s->avctx->ticks_per_frame: %d\n",
+           s->avctx->ticks_per_frame);
+    av_log(s->avctx, AV_LOG_DEBUG, "ideal: %g\n", ideal);
+    av_log(s->avctx, AV_LOG_DEBUG, "total_bits: %g\n", s->total_bits);
+    if (ideal + use15_7_threshold < s->total_bits)
+        return 1;
+    else
+        return 0;
+#else
+    return s->avctx->global_quality == 0;
+#endif
+}
+
+static const double color15_7_factor = 100;
+
+static int optimum_dist(FlashSV2Context * s)
+{
+#ifndef FLASHSV2_DUMB
+    double ideal =
+        s->avctx->bit_rate * s->avctx->time_base.den *
+        s->avctx->ticks_per_frame;
+    int dist = pow((s->total_bits / ideal) * color15_7_factor, 3);
+    av_log(s->avctx, AV_LOG_DEBUG, "dist: %d\n", dist);
+    return dist;
+#else
+    return 15;
+#endif
+}
+
+
+static int reconfigure_at_keyframe(FlashSV2Context * s, uint8_t * image,
+                                   int stride)
+{
+    int update_pallet = 0;
+    int res;
+    s->block_width = optimum_block_width(s);
+    s->block_height = optimum_block_height(s);
+
+    s->rows = (s->image_height + s->block_height - 1) / s->block_height;
+    s->cols = (s->image_width + s->block_width - 1) / s->block_width;
+
+    if (s->rows * s->cols != s->blocks_size / sizeof(Block)) {
+        if (s->rows * s->cols > s->blocks_size / sizeof(Block)) {
+            s->frame_blocks =
+                av_realloc(s->frame_blocks,
+                           s->rows * s->cols * sizeof(Block));
+            s->key_blocks =
+                av_realloc(s->key_blocks,
+                           s->cols * s->rows * sizeof(Block));
+            if (!s->frame_blocks || !s->key_blocks) {
+                av_log(s->avctx, AV_LOG_ERROR,
+                       "Memory allocation failed.\n");
+                return -1;
+            }
+            s->blocks_size = s->rows * s->cols * sizeof(Block);
+        }
+        init_blocks(s, s->frame_blocks, s->encbuffer, s->databuffer);
+        init_blocks(s, s->key_blocks, s->keybuffer, 0);
+
+    }
+
+    s->use15_7 = optimum_use15_7(s);
+    if (s->use15_7) {
+        if ((s->use_custom_pallet && s->pallet_type != 1) || update_pallet) {
+            res =
+                generate_optimum_pallet(&s->pallet, image, s->image_width,
+                                        s->image_height, stride);
+            if (res != 0)
+                return res;
+            s->pallet_type = 1;
+            av_log(s->avctx, AV_LOG_DEBUG, "Generated optimum pallet\n");
+        } else if (!s->use_custom_pallet && s->pallet_type != 0) {
+            res = generate_default_pallet(&s->pallet);
+            if (res != 0)
+                return res;
+            s->pallet_type = 0;
+            av_log(s->avctx, AV_LOG_DEBUG, "Generated default pallet\n");
+        }
+    }
+
+
+    reset_stats(s);
+
+    return 0;
+}
+
+static const int min_frames = 2;
+
+static int flashsv2_encode_frame(AVCodecContext * avctx, uint8_t * buf,
+                                 int buf_size, void *data)
+{
+    FlashSV2Context *const s = avctx->priv_data;
+    AVFrame *pict = data;
+    AVFrame *const p = &s->frame;
+    int res;
+    int keyframe = 0;
+
+    *p = *pict;
+
+    /* First frame needs to be a keyframe */
+    if (avctx->frame_number == 0) {
+        keyframe = 1;
+    }
+
+    /* Check the placement of keyframes */
+    if (avctx->gop_size > 0) {
+        if (avctx->frame_number >= s->last_key_frame + avctx->gop_size)
+            keyframe = 1;
+    }
+
+    if (buf_size < s->frame_size) {
+        //Conservative upper bound check for compressed data
+        av_log(avctx, AV_LOG_ERROR, "buf_size %d <  %d\n", buf_size,
+               s->frame_size);
+        return -1;
+    }
+
+    if (!keyframe
+        && avctx->frame_number > s->last_key_frame + avctx->keyint_min) {
+        recommend_keyframe(s, &keyframe);
+        if (keyframe)
+            av_log(avctx, AV_LOG_DEBUG,
+                   "Recommending key frame at frame %d\n",
+                   avctx->frame_number);
+    }
+
+    if (keyframe) {
+        res = reconfigure_at_keyframe(s, p->data[0], p->linesize[0]);
+        if (res != 0)
+            return res;
+    }
+
+    if (s->use15_7)
+        s->dist = optimum_dist(s);
+
+    res =
+        write_bitstream(s, p->data[0], p->linesize[0], buf, buf_size,
+                        keyframe);
+
+    if (keyframe) {
+        new_key_frame(s);
+        p->pict_type = FF_I_TYPE;
+        p->key_frame = 1;
+        s->last_key_frame = avctx->frame_number;
+        av_log(avctx, AV_LOG_DEBUG, "Inserting key frame at frame %d\n",
+               avctx->frame_number);
+    } else {
+        p->pict_type = FF_P_TYPE;
+        p->key_frame = 0;
+    }
+
+    avctx->coded_frame = p;
+
+    return res;
+}
+
+static av_cold int flashsv2_encode_end(AVCodecContext * avctx)
+{
+    FlashSV2Context *s = avctx->priv_data;
+
+    cleanup(s);
+
+    return 0;
+}
+
+AVCodec flashsv2_encoder = {
+    "flashsv2",
+    CODEC_TYPE_VIDEO,
+    CODEC_ID_FLASHSV2,
+    sizeof(FlashSV2Context),
+    flashsv2_encode_init,
+    flashsv2_encode_frame,
+    flashsv2_encode_end,
+    .pix_fmts = (enum PixelFormat[]) {PIX_FMT_BGR24, PIX_FMT_NONE},
+    .long_name = NULL_IF_CONFIG_SMALL("Flash Screen Video Version 2"),
+};
Index: libavformat/flvenc.c
===================================================================
--- libavformat/flvenc.c	(revision 19479)
+++ libavformat/flvenc.c	(working copy)
@@ -29,6 +29,7 @@
 static const AVCodecTag flv_video_codec_ids[] = {
     {CODEC_ID_FLV1,    FLV_CODECID_H263  },
     {CODEC_ID_FLASHSV, FLV_CODECID_SCREEN},
+    {CODEC_ID_FLASHSV2, FLV_CODECID_SCREEN2},
     {CODEC_ID_VP6F,    FLV_CODECID_VP6   },
     {CODEC_ID_VP6,     FLV_CODECID_VP6   },
     {CODEC_ID_H264,    FLV_CODECID_H264  },



More information about the ffmpeg-devel mailing list