diff mbox

[FFmpeg-devel] avcodec: add Mandsoft Screen Capture Codec decoder

Message ID 20170415114953.6472-1-onemda@gmail.com
State Superseded
Headers show

Commit Message

Paul B Mahol April 15, 2017, 11:49 a.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 configure               |   1 +
 doc/general.texi        |   1 +
 libavcodec/Makefile     |   1 +
 libavcodec/allcodecs.c  |   1 +
 libavcodec/avcodec.h    |   1 +
 libavcodec/codec_desc.c |   7 ++
 libavcodec/mscc.c       | 220 ++++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/riff.c      |   1 +
 8 files changed, 233 insertions(+)
 create mode 100644 libavcodec/mscc.c

Comments

Carl Eugen Hoyos April 15, 2017, 2:44 p.m. UTC | #1
2017-04-15 13:49 GMT+02:00 Paul B Mahol <onemda@gmail.com>:

> +    case 32: avctx->pix_fmt = AV_PIX_FMT_BGRA;   break;

(I am thankful for this patch and, yes, I believe we had this
discussion before.)
If this is a screen capture codec, I believe BGR0 is the correct
colorspace. I understand that you tested that the encoder can
really encode transparency but the issue - imo - is, that if the
codec is (or actually was) used to record 32bit Windows screens
then some parts of the screen will contain "0xff" as alpha value
and others (sprites?) will contain "0x00" producing funny output
if you just transcode to png.

In the past, I also put BGRA there (for some other codec) and
it was fixed after another developer tested the same sample I
had tested but (correctly!) explained that the output (that I
considered useful originally) makes no sense and the
colorspace (or actually the transparency in the output file) is
wrong.

(If this is not a screen capture codec, BGRA is of course
most likely correct.)

Please don't let this stop you!

Carl Eugen
Paul B Mahol April 15, 2017, 3:59 p.m. UTC | #2
On 4/15/17, Carl Eugen Hoyos <ceffmpeg@gmail.com> wrote:
> 2017-04-15 13:49 GMT+02:00 Paul B Mahol <onemda@gmail.com>:
>
>> +    case 32: avctx->pix_fmt = AV_PIX_FMT_BGRA;   break;
>
> (I am thankful for this patch and, yes, I believe we had this
> discussion before.)
> If this is a screen capture codec, I believe BGR0 is the correct
> colorspace. I understand that you tested that the encoder can
> really encode transparency but the issue - imo - is, that if the
> codec is (or actually was) used to record 32bit Windows screens
> then some parts of the screen will contain "0xff" as alpha value
> and others (sprites?) will contain "0x00" producing funny output
> if you just transcode to png.
>
> In the past, I also put BGRA there (for some other codec) and
> it was fixed after another developer tested the same sample I
> had tested but (correctly!) explained that the output (that I
> considered useful originally) makes no sense and the
> colorspace (or actually the transparency in the output file) is
> wrong.
>
> (If this is not a screen capture codec, BGRA is of course
> most likely correct.)
>
> Please don't let this stop you!

Using bgr0 when there is already valid transparency is bad as
that will kill it, on other side if its bgra like it is now
one can just ignore alpha component if its not interesting.
diff mbox

Patch

diff --git a/configure b/configure
index 98f7828..1436acb 100755
--- a/configure
+++ b/configure
@@ -2488,6 +2488,7 @@  mpeg2video_encoder_select="aandcttables mpegvideoenc h263dsp"
 mpeg4_decoder_select="h263_decoder mpeg4video_parser"
 mpeg4_encoder_select="h263_encoder"
 msa1_decoder_select="mss34dsp"
+mscc_decoder_select="zlib"
 msmpeg4v1_decoder_select="h263_decoder"
 msmpeg4v2_decoder_select="h263_decoder"
 msmpeg4v2_encoder_select="h263_encoder"
diff --git a/doc/general.texi b/doc/general.texi
index a02437b..72f02b1 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -789,6 +789,7 @@  following image formats are supported:
     @tab Used in LucasArts games / SMUSH animations.
 @item lossless MJPEG         @tab  X  @tab  X
 @item MagicYUV Video         @tab     @tab  X
+@item Mandsoft Screen Capture Codec  @tab     @tab  X
 @item Microsoft ATC Screen   @tab     @tab  X
     @tab Also known as Microsoft Screen 3.
 @item Microsoft Expression Encoder Screen  @tab     @tab  X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index c745454..cf5317d 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -429,6 +429,7 @@  OBJS-$(CONFIG_MPEG4_MEDIACODEC_DECODER) += mediacodecdec.o
 OBJS-$(CONFIG_MPEG4_OMX_ENCODER)       += omx.o
 OBJS-$(CONFIG_MPL2_DECODER)            += mpl2dec.o ass.o
 OBJS-$(CONFIG_MSA1_DECODER)            += mss3.o
+OBJS-$(CONFIG_MSCC_DECODER)            += mscc.o
 OBJS-$(CONFIG_MSMPEG4V1_DECODER)       += msmpeg4dec.o msmpeg4.o msmpeg4data.o
 OBJS-$(CONFIG_MSMPEG4V2_DECODER)       += msmpeg4dec.o msmpeg4.o msmpeg4data.o
 OBJS-$(CONFIG_MSMPEG4V2_ENCODER)       += msmpeg4enc.o msmpeg4.o msmpeg4data.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 379bd6e..5a708b3 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -258,6 +258,7 @@  static void register_all(void)
     REGISTER_DECODER(MPEG2_CRYSTALHD,   mpeg2_crystalhd);
     REGISTER_DECODER(MPEG2_QSV,         mpeg2_qsv);
     REGISTER_DECODER(MSA1,              msa1);
+    REGISTER_DECODER(MSCC,              mscc);
     REGISTER_DECODER(MSMPEG4V1,         msmpeg4v1);
     REGISTER_ENCDEC (MSMPEG4V2,         msmpeg4v2);
     REGISTER_ENCDEC (MSMPEG4V3,         msmpeg4v3);
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index ee13371..da9d9dc 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -443,6 +443,7 @@  enum AVCodecID {
     AV_CODEC_ID_XPM,
     AV_CODEC_ID_AV1,
     AV_CODEC_ID_BITPACKED,
+    AV_CODEC_ID_MSCC,
 
     /* various PCM "codecs" */
     AV_CODEC_ID_FIRST_AUDIO = 0x10000,     ///< A dummy id pointing at the start of audio codecs
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 7b2a1b9..e342db7 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -1388,6 +1388,13 @@  static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("Bitpacked"),
         .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSLESS,
     },
+    {
+        .id        = AV_CODEC_ID_MSCC,
+        .type      = AVMEDIA_TYPE_VIDEO,
+        .name      = "mscc",
+        .long_name = NULL_IF_CONFIG_SMALL("Mandsoft Screen Capture Codec"),
+        .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSLESS,
+    },
 
     /* image codecs */
     {
diff --git a/libavcodec/mscc.c b/libavcodec/mscc.c
new file mode 100644
index 0000000..6050056
--- /dev/null
+++ b/libavcodec/mscc.c
@@ -0,0 +1,220 @@ 
+/*
+ * Mandsoft Screen Capture Codec decoder
+ *
+ * Copyright (c) 2017 Paul B Mahol
+ *
+ * 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
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "avcodec.h"
+#include "bytestream.h"
+#include "internal.h"
+
+#include <zlib.h>
+
+typedef struct MSCCContext {
+    unsigned          bpp;
+    unsigned int      decomp_size;
+    uint8_t          *decomp_buf;
+    unsigned int      uncomp_size;
+    uint8_t          *uncomp_buf;
+    z_stream          zstream;
+} MSCCContext;
+
+static int decode_frame(AVCodecContext *avctx,
+                        void *data, int *got_frame,
+                        AVPacket *avpkt)
+{
+    MSCCContext *s = avctx->priv_data;
+    AVFrame *frame = data;
+    GetByteContext gb;
+    PutByteContext pb;
+    int ret, j;
+
+    if (avpkt->size < 3)
+        return AVERROR_INVALIDDATA;
+    if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
+        return ret;
+
+    avpkt->data[2] ^= avpkt->data[0];
+
+    ret = inflateReset(&s->zstream);
+    if (ret != Z_OK) {
+        av_log(avctx, AV_LOG_ERROR, "Inflate reset error: %d\n", ret);
+        return AVERROR_UNKNOWN;
+    }
+    s->zstream.next_in   = avpkt->data + 2;
+    s->zstream.avail_in  = avpkt->size - 2;
+    s->zstream.next_out  = s->decomp_buf;
+    s->zstream.avail_out = s->decomp_size;
+    ret = inflate(&s->zstream, Z_FINISH);
+    if (ret != Z_STREAM_END) {
+        av_log(avctx, AV_LOG_ERROR, "Inflate error: %d\n", ret);
+        return AVERROR_UNKNOWN;
+    }
+
+    bytestream2_init(&gb, s->decomp_buf, s->zstream.avail_out);
+    bytestream2_init_writer(&pb, s->uncomp_buf, s->uncomp_size);
+
+    while (bytestream2_get_bytes_left(&gb) > 0) {
+        unsigned fill, run = bytestream2_get_byte(&gb);
+
+        if (run) {
+            switch (avctx->bits_per_coded_sample) {
+            case 8:
+                fill = bytestream2_get_byte(&gb);
+                break;
+            case 16:
+                fill = bytestream2_get_le16(&gb);
+                break;
+            case 24:
+                fill = bytestream2_get_le24(&gb);
+                break;
+            case 32:
+                fill = bytestream2_get_le32(&gb);
+                break;
+            }
+
+            for (j = 0; j < run; j++) {
+                switch (avctx->bits_per_coded_sample) {
+                case 8:
+                    bytestream2_put_byte(&pb, fill);
+                    break;
+                case 16:
+                    bytestream2_put_le16(&pb, fill);
+                    break;
+                case 24:
+                    bytestream2_put_le24(&pb, fill);
+                    break;
+                case 32:
+                    bytestream2_put_le32(&pb, fill);
+                    break;
+                }
+            }
+        } else {
+            unsigned copy = bytestream2_get_byte(&gb);
+
+            if (copy == 1) {
+                break;
+            } else if (copy == 2) {
+                unsigned x, y;
+
+                x = bytestream2_get_byte(&gb);
+                y = bytestream2_get_byte(&gb);
+
+                bytestream2_skip_p(&pb, x * s->bpp);
+                bytestream2_skip_p(&pb, y * s->bpp * avctx->width);
+            } else {
+                for (j = 0; j < copy; j++) {
+                    switch (avctx->bits_per_coded_sample) {
+                    case 8:
+                        bytestream2_put_byte(&pb, bytestream2_get_byte(&gb));
+                        break;
+                    case 16:
+                        bytestream2_put_le16(&pb, bytestream2_get_le16(&gb));
+                        break;
+                    case 24:
+                        bytestream2_put_le24(&pb, bytestream2_get_le24(&gb));
+                        break;
+                    case 32:
+                        bytestream2_put_le32(&pb, bytestream2_get_le32(&gb));
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    for (j = 0; j < avctx->height; j++) {
+        memcpy(frame->data[0] + (avctx->height - j - 1) * frame->linesize[0],
+               s->uncomp_buf + s->bpp * j * avctx->width, s->bpp * avctx->width);
+    }
+
+    frame->key_frame = 1;
+    frame->pict_type = AV_PICTURE_TYPE_I;
+
+    *got_frame = 1;
+
+    return avpkt->size;
+}
+
+static av_cold int decode_init(AVCodecContext *avctx)
+{
+    MSCCContext *s = avctx->priv_data;
+    int zret;
+
+    switch (avctx->bits_per_coded_sample) {
+    case  8: avctx->pix_fmt = AV_PIX_FMT_GRAY8;  break;
+    case 16: avctx->pix_fmt = AV_PIX_FMT_RGB555; break;
+    case 24: avctx->pix_fmt = AV_PIX_FMT_BGR24;  break;
+    case 32: avctx->pix_fmt = AV_PIX_FMT_BGRA;   break;
+    default:
+        av_log(avctx, AV_LOG_ERROR, "Unsupported bitdepth %i\n", avctx->bits_per_coded_sample);
+        return AVERROR_INVALIDDATA;
+    }
+
+    s->bpp = avctx->bits_per_coded_sample >> 3;
+    memset(&s->zstream, 0, sizeof(z_stream));
+
+    s->decomp_size = 4 * avctx->height * ((avctx->width * avctx->bits_per_coded_sample + 31) / 32);
+    if (!(s->decomp_buf = av_malloc(s->decomp_size)))
+        return AVERROR(ENOMEM);
+
+    s->uncomp_size = 4 * avctx->height * ((avctx->width * avctx->bits_per_coded_sample + 31) / 32);
+    if (!(s->uncomp_buf = av_malloc(s->uncomp_size)))
+        return AVERROR(ENOMEM);
+
+    s->zstream.zalloc = Z_NULL;
+    s->zstream.zfree = Z_NULL;
+    s->zstream.opaque = Z_NULL;
+    zret = inflateInit(&s->zstream);
+    if (zret != Z_OK) {
+        av_log(avctx, AV_LOG_ERROR, "Inflate init error: %d\n", zret);
+        return AVERROR_UNKNOWN;
+    }
+
+    return 0;
+}
+
+static av_cold int decode_close(AVCodecContext *avctx)
+{
+    MSCCContext *s = avctx->priv_data;
+
+    av_freep(&s->decomp_buf);
+    s->decomp_size = 0;
+    av_freep(&s->uncomp_buf);
+    s->uncomp_size = 0;
+    inflateEnd(&s->zstream);
+
+    return 0;
+}
+
+AVCodec ff_mscc_decoder = {
+    .name             = "mscc",
+    .long_name        = NULL_IF_CONFIG_SMALL("Mandsoft Screen Capture Codec"),
+    .type             = AVMEDIA_TYPE_VIDEO,
+    .id               = AV_CODEC_ID_MSCC,
+    .priv_data_size   = sizeof(MSCCContext),
+    .init             = decode_init,
+    .close            = decode_close,
+    .decode           = decode_frame,
+    .capabilities     = AV_CODEC_CAP_DR1,
+};
diff --git a/libavformat/riff.c b/libavformat/riff.c
index 2f2f537..294d11b 100644
--- a/libavformat/riff.c
+++ b/libavformat/riff.c
@@ -452,6 +452,7 @@  const AVCodecTag ff_codec_bmp_tags[] = {
     { AV_CODEC_ID_SCPR,         MKTAG('S', 'C', 'P', 'R') },
     { AV_CODEC_ID_CLEARVIDEO,   MKTAG('U', 'C', 'O', 'D') },
     { AV_CODEC_ID_AV1,          MKTAG('A', 'V', '0', '1') },
+    { AV_CODEC_ID_MSCC,         MKTAG('M', 'S', 'C', 'C') },
     { AV_CODEC_ID_NONE,         0 }
 };