Screenpresso SPV1 decoder
authorVittorio Giovara <vittorio.giovara@gmail.com>
Thu, 24 Sep 2015 22:36:50 +0000 (00:36 +0200)
committerVittorio Giovara <vittorio.giovara@gmail.com>
Wed, 30 Sep 2015 14:45:11 +0000 (16:45 +0200)
Signed-off-by: Vittorio Giovara <vittorio.giovara@gmail.com>
12 files changed:
Changelog
configure
doc/general.texi
libavcodec/Makefile
libavcodec/allcodecs.c
libavcodec/avcodec.h
libavcodec/codec_desc.c
libavcodec/screenpresso.c [new file with mode: 0644]
libavcodec/version.h
libavformat/riff.c
tests/fate/video.mak
tests/ref/fate/screenpresso [new file with mode: 0644]

index 461c5b1..696c881 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -43,6 +43,7 @@ version <next>:
 - Intel QSV-accelerated MPEG-2 video and HEVC decoding
 - Support DNx100 (1440x1080@8)
 - DXV decoding
+- Screenpresso SPV1 decoding
 
 
 version 11:
index 58a8cb6..b4eccd6 100755 (executable)
--- a/configure
+++ b/configure
@@ -1972,6 +1972,7 @@ rv20_decoder_select="error_resilience h263_decoder h263dsp mpeg_er"
 rv20_encoder_select="h263_encoder"
 rv30_decoder_select="error_resilience golomb h264chroma h264pred h264qpel mpeg_er mpegvideo rv34dsp videodsp"
 rv40_decoder_select="error_resilience golomb h264chroma h264pred h264qpel mpeg_er mpegvideo rv34dsp videodsp"
+screenpresso_decoder_deps="zlib"
 shorten_decoder_select="golomb"
 sipr_decoder_select="lsp"
 sp5x_decoder_select="mjpeg_decoder"
index 7cd7693..6e43269 100644 (file)
@@ -409,6 +409,7 @@ library:
 @item RTP                       @tab X @tab X
 @item RTSP                      @tab X @tab X
 @item SAP                       @tab X @tab X
+@item Screenpresso              @tab   @tab X
 @item SDP                       @tab   @tab X
 @item Sega FILM/CPK             @tab   @tab X
     @tab Used in many Sega Saturn console games.
index f163a79..632a8a6 100644 (file)
@@ -385,6 +385,7 @@ OBJS-$(CONFIG_RV30_DECODER)            += rv30.o rv34.o rv30dsp.o
 OBJS-$(CONFIG_RV40_DECODER)            += rv40.o rv34.o rv40dsp.o
 OBJS-$(CONFIG_S302M_DECODER)           += s302m.o
 OBJS-$(CONFIG_SANM_DECODER)            += sanm.o
+OBJS-$(CONFIG_SCREENPRESSO_DECODER)    += screenpresso.o
 OBJS-$(CONFIG_SGI_DECODER)             += sgidec.o
 OBJS-$(CONFIG_SGI_ENCODER)             += sgienc.o rle.o
 OBJS-$(CONFIG_SGIRLE_DECODER)          += sgirledec.o
index 49f5a70..2d8474a 100644 (file)
@@ -249,6 +249,7 @@ void avcodec_register_all(void)
     REGISTER_DECODER(RV40,              rv40);
     REGISTER_DECODER(S302M,             s302m);
     REGISTER_DECODER(SANM,              sanm);
+    REGISTER_DECODER(SCREENPRESSO,      screenpresso);
     REGISTER_ENCDEC (SGI,               sgi);
     REGISTER_DECODER(SGIRLE,            sgirle);
     REGISTER_DECODER(SMACKER,           smacker);
index 9c6fde0..11ae1fc 100644 (file)
@@ -299,6 +299,7 @@ enum AVCodecID {
     AV_CODEC_ID_HAP,
     AV_CODEC_ID_DDS,
     AV_CODEC_ID_DXV,
+    AV_CODEC_ID_SCREENPRESSO,
 
     /* various PCM "codecs" */
     AV_CODEC_ID_FIRST_AUDIO = 0x10000,     ///< A dummy id pointing at the start of audio codecs
index 31d3555..d2c7a91 100644 (file)
@@ -1162,6 +1162,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("Resolume DXV"),
         .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
     },
+    {
+        .id        = AV_CODEC_ID_SCREENPRESSO,
+        .type      = AVMEDIA_TYPE_VIDEO,
+        .name      = "screenpresso",
+        .long_name = NULL_IF_CONFIG_SMALL("Screenpresso"),
+        .props     = AV_CODEC_PROP_LOSSLESS,
+    },
 
     /* image codecs */
     {
diff --git a/libavcodec/screenpresso.c b/libavcodec/screenpresso.c
new file mode 100644 (file)
index 0000000..d25d0eb
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Screenpresso decoder
+ * Copyright (C) 2015 Vittorio Giovara <vittorio.giovara@gmail.com>
+ *
+ * This file is part of Libav.
+ *
+ * Libav 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.
+ *
+ * Libav 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 Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Screenpresso decoder
+ *
+ * Fourcc: SPV1
+ *
+ * Screenpresso simply horizontally flips and then deflates frames,
+ * alternating full pictures and deltas. Deltas are related to the currently
+ * rebuilt frame (not the reference), and since there is no coordinate system
+ * they contain exactly as many pixel as the keyframe.
+ *
+ * Supports: BGR24
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <zlib.h>
+
+#include "libavutil/imgutils.h"
+#include "libavutil/internal.h"
+
+#include "avcodec.h"
+#include "internal.h"
+
+typedef struct ScreenpressoContext {
+    AVFrame *current;
+
+    /* zlib interation */
+    uint8_t *inflated_buf;
+    uLongf inflated_size;
+} ScreenpressoContext;
+
+static av_cold int screenpresso_close(AVCodecContext *avctx)
+{
+    ScreenpressoContext *ctx = avctx->priv_data;
+
+    av_frame_free(&ctx->current);
+    av_freep(&ctx->inflated_buf);
+
+    return 0;
+}
+
+static av_cold int screenpresso_init(AVCodecContext *avctx)
+{
+    ScreenpressoContext *ctx = avctx->priv_data;
+
+    /* These needs to be set to estimate uncompressed buffer */
+    int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx);
+    if (ret < 0) {
+        av_log(avctx, AV_LOG_ERROR, "Invalid image size %dx%d.\n",
+               avctx->width, avctx->height);
+        return ret;
+    }
+
+    /* Allocate current frame */
+    ctx->current = av_frame_alloc();
+    if (!ctx->current)
+        return AVERROR(ENOMEM);
+
+    avctx->pix_fmt = AV_PIX_FMT_BGR24;
+
+    return 0;
+}
+
+static void sum_delta_flipped(uint8_t       *dst, int dst_linesize,
+                              const uint8_t *src, int src_linesize,
+                              int bytewidth, int height)
+{
+    int i;
+    for (; height > 0; height--) {
+        for (i = 0; i < bytewidth; i++)
+            dst[i] += src[(height - 1) * src_linesize + i];
+        dst += dst_linesize;
+    }
+}
+
+static int screenpresso_decode_frame(AVCodecContext *avctx, void *data,
+                                     int *got_frame, AVPacket *avpkt)
+{
+    ScreenpressoContext *ctx = avctx->priv_data;
+    AVFrame *frame = data;
+    int keyframe;
+    int ret;
+
+    /* Size check */
+    if (avpkt->size < 3) {
+        av_log(avctx, AV_LOG_ERROR, "Packet too small (%d)\n", avpkt->size);
+        return AVERROR_INVALIDDATA;
+    }
+
+    /* Basic sanity check, but not really harmful */
+    if ((avpkt->data[0] != 0x73 && avpkt->data[0] != 0x72) ||
+        avpkt->data[1] != 8) { // bpp probably
+        av_log(avctx, AV_LOG_WARNING, "Unknown header 0x%02X%02X\n",
+               avpkt->data[0], avpkt->data[1]);
+    }
+    keyframe = (avpkt->data[0] == 0x73);
+
+    /* Resize deflate buffer and frame on resolution change */
+    if (ctx->inflated_size != avctx->width * avctx->height * 3) {
+        av_frame_unref(ctx->current);
+        ret = ff_get_buffer(avctx, ctx->current, AV_GET_BUFFER_FLAG_REF);
+        if (ret < 0)
+            return ret;
+
+        /* If malloc fails, reset len to avoid preserving an invalid value */
+        ctx->inflated_size = avctx->width * avctx->height * 3;
+        ret = av_reallocp(&ctx->inflated_buf, ctx->inflated_size);
+        if (ret < 0) {
+            ctx->inflated_size = 0;
+            return ret;
+        }
+    }
+
+    /* Inflate the frame after the 2 byte header */
+    ret = uncompress(ctx->inflated_buf, &ctx->inflated_size,
+                     avpkt->data + 2, avpkt->size - 2);
+    if (ret) {
+        av_log(avctx, AV_LOG_ERROR, "Deflate error %d.\n", ret);
+        return AVERROR_UNKNOWN;
+    }
+
+    /* When a keyframe is found, copy it (flipped) */
+    if (keyframe)
+        av_image_copy_plane(ctx->current->data[0] +
+                            ctx->current->linesize[0] * (avctx->height - 1),
+                            -1 * ctx->current->linesize[0],
+                            ctx->inflated_buf, avctx->width * 3,
+                            avctx->width * 3, avctx->height);
+    /* Otherwise sum the delta on top of the current frame */
+    else
+        sum_delta_flipped(ctx->current->data[0], ctx->current->linesize[0],
+                          ctx->inflated_buf, avctx->width * 3,
+                          avctx->width * 3, avctx->height);
+
+    /* Frame is ready to be output */
+    ret = av_frame_ref(frame, ctx->current);
+    if (ret < 0)
+        return ret;
+
+    /* Usual properties */
+    if (keyframe) {
+        frame->pict_type = AV_PICTURE_TYPE_I;
+        frame->key_frame = 1;
+    } else {
+        frame->pict_type = AV_PICTURE_TYPE_P;
+    }
+    *got_frame = 1;
+
+    return 0;
+}
+
+AVCodec ff_screenpresso_decoder = {
+    .name           = "screenpresso",
+    .long_name      = NULL_IF_CONFIG_SMALL("Screenpresso"),
+    .type           = AVMEDIA_TYPE_VIDEO,
+    .id             = AV_CODEC_ID_SCREENPRESSO,
+    .init           = screenpresso_init,
+    .decode         = screenpresso_decode_frame,
+    .close          = screenpresso_close,
+    .priv_data_size = sizeof(ScreenpressoContext),
+    .capabilities   = AV_CODEC_CAP_DR1,
+    .caps_internal  = FF_CODEC_CAP_INIT_THREADSAFE |
+                      FF_CODEC_CAP_INIT_CLEANUP,
+};
index 80b0ba5..4b487ca 100644 (file)
@@ -29,7 +29,7 @@
 #include "libavutil/version.h"
 
 #define LIBAVCODEC_VERSION_MAJOR 57
-#define LIBAVCODEC_VERSION_MINOR  2
+#define LIBAVCODEC_VERSION_MINOR  3
 #define LIBAVCODEC_VERSION_MICRO  0
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
index 1746297..f0e99df 100644 (file)
@@ -360,6 +360,7 @@ const AVCodecTag ff_codec_bmp_tags[] = {
     { AV_CODEC_ID_TDSC,         MKTAG('T', 'D', 'S', 'C') },
     { AV_CODEC_ID_HQ_HQA,       MKTAG('C', 'U', 'V', 'C') },
     { AV_CODEC_ID_RV40,         MKTAG('R', 'V', '4', '0') },
+    { AV_CODEC_ID_SCREENPRESSO, MKTAG('S', 'P', 'V', '1') },
     { AV_CODEC_ID_NONE,         0 }
 };
 
index ef1d41d..47865e4 100644 (file)
@@ -275,6 +275,9 @@ fate-roqvideo: CMD = framecrc -i $(TARGET_SAMPLES)/idroq/idlogo.roq -an
 FATE_SAMPLES_AVCONV-$(call DEMDEC, SMUSH, SANM) += fate-sanm
 fate-sanm: CMD = framecrc -i $(TARGET_SAMPLES)/smush/ronin_part.znm -an -pix_fmt rgb24
 
+FATE_SAMPLES_AVCONV-$(call DEMDEC, AVI, SCREENPRESSO) += fate-screenpresso
+fate-screenpresso: CMD = framecrc -i $(TARGET_SAMPLES)/spv1/bunny.avi
+
 FATE_SAMPLES_AVCONV-$(call DEMDEC, VMD, VMDVIDEO) += fate-sierra-vmd-video
 fate-sierra-vmd-video: CMD = framecrc -i $(TARGET_SAMPLES)/vmd/12.vmd -pix_fmt rgb24 -an
 
diff --git a/tests/ref/fate/screenpresso b/tests/ref/fate/screenpresso
new file mode 100644 (file)
index 0000000..bbdffb4
--- /dev/null
@@ -0,0 +1,5 @@
+#tb 0: 1/15
+0,          0,          0,        1,   691200, 0xfdbdfad6
+0,          1,          1,        1,   691200, 0xc5feb961
+0,          4,          4,        1,   691200, 0x4c8c7e23
+0,          8,          8,        1,   691200, 0xd95c89f8