diff mbox

[FFmpeg-devel,2/2] avcodec: add WinCAM Motion Video decoder

Message ID 20180825200356.28361-2-onemda@gmail.com
State New
Headers show

Commit Message

Paul B Mahol Aug. 25, 2018, 8:03 p.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 configure               |   1 +
 libavcodec/Makefile     |   1 +
 libavcodec/allcodecs.c  |   1 +
 libavcodec/avcodec.h    |   1 +
 libavcodec/codec_desc.c |   7 ++
 libavcodec/wcmv.c       | 247 ++++++++++++++++++++++++++++++++++++++++
 libavformat/riff.c      |   1 +
 7 files changed, 259 insertions(+)
 create mode 100644 libavcodec/wcmv.c

Comments

Rostislav Pehlivanov Aug. 27, 2018, 6:31 p.m. UTC | #1
On Sat, 25 Aug 2018 at 21:12, Paul B Mahol <onemda@gmail.com> wrote:

> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  configure               |   1 +
>  libavcodec/Makefile     |   1 +
>  libavcodec/allcodecs.c  |   1 +
>  libavcodec/avcodec.h    |   1 +
>  libavcodec/codec_desc.c |   7 ++
>  libavcodec/wcmv.c       | 247 ++++++++++++++++++++++++++++++++++++++++
>  libavformat/riff.c      |   1 +
>  7 files changed, 259 insertions(+)
>  create mode 100644 libavcodec/wcmv.c
>
> diff --git a/configure b/configure
> index 0c1f6a79a7..469797935e 100755
> --- a/configure
> +++ b/configure
> @@ -2750,6 +2750,7 @@ vp6f_decoder_select="vp6_decoder"
>  vp7_decoder_select="h264pred videodsp vp8dsp"
>  vp8_decoder_select="h264pred videodsp vp8dsp"
>  vp9_decoder_select="videodsp vp9_parser vp9_superframe_split_bsf"
> +wcmv_decoder_deps="zlib"
>  webp_decoder_select="vp8_decoder exif"
>  wmalossless_decoder_select="llauddsp"
>  wmapro_decoder_select="mdct sinewin wma_freqs"
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index aee4f5431a..f8673f0121 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -677,6 +677,7 @@ OBJS-$(CONFIG_VP9_V4L2M2M_DECODER)     +=
> v4l2_m2m_dec.o
>  OBJS-$(CONFIG_VQA_DECODER)             += vqavideo.o
>  OBJS-$(CONFIG_WAVPACK_DECODER)         += wavpack.o
>  OBJS-$(CONFIG_WAVPACK_ENCODER)         += wavpackenc.o
> +OBJS-$(CONFIG_WCMV_DECODER)            += wcmv.o
>  OBJS-$(CONFIG_WEBP_DECODER)            += webp.o
>  OBJS-$(CONFIG_WEBVTT_DECODER)          += webvttdec.o ass.o
>  OBJS-$(CONFIG_WEBVTT_ENCODER)          += webvttenc.o ass_split.o
> diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
> index d41868c591..42d98f9b0a 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -338,6 +338,7 @@ extern AVCodec ff_vp9_v4l2m2m_decoder;
>  extern AVCodec ff_vqa_decoder;
>  extern AVCodec ff_bitpacked_decoder;
>  extern AVCodec ff_webp_decoder;
> +extern AVCodec ff_wcmv_decoder;
>  extern AVCodec ff_wrapped_avframe_encoder;
>  extern AVCodec ff_wrapped_avframe_decoder;
>  extern AVCodec ff_wmv1_encoder;
> diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
> index be41b8cf0e..b6688b7af3 100644
> --- a/libavcodec/avcodec.h
> +++ b/libavcodec/avcodec.h
> @@ -450,6 +450,7 @@ enum AVCodecID {
>      AV_CODEC_ID_IMM4,
>      AV_CODEC_ID_PROSUMER,
>      AV_CODEC_ID_MWSC,
> +    AV_CODEC_ID_WCMV,
>
>      /* 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 129d0f1aac..46dfe3f5e5 100644
> --- a/libavcodec/codec_desc.c
> +++ b/libavcodec/codec_desc.c
> @@ -1675,6 +1675,13 @@ static const AVCodecDescriptor codec_descriptors[]
> = {
>          .long_name = NULL_IF_CONFIG_SMALL("MatchWare Screen Capture
> Codec"),
>          .props     = AV_CODEC_PROP_LOSSLESS,
>      },
> +    {
> +        .id        = AV_CODEC_ID_WCMV,
> +        .type      = AVMEDIA_TYPE_VIDEO,
> +        .name      = "wcmv",
> +        .long_name = NULL_IF_CONFIG_SMALL("WinCAM Motion Video"),
> +        .props     = AV_CODEC_PROP_LOSSLESS,
> +    },
>
>      /* various PCM "codecs" */
>      {
> diff --git a/libavcodec/wcmv.c b/libavcodec/wcmv.c
> new file mode 100644
> index 0000000000..74cb1166bb
> --- /dev/null
> +++ b/libavcodec/wcmv.c
> @@ -0,0 +1,247 @@
> +/*
> + * WinCAM Motion Video decoder
> + *
> + * Copyright (c) 2018 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 WCMVContext {
> +    z_stream    zstream;
> +    AVFrame    *prev_frame;
> +    uint8_t     block_data[65536*8];
> +} WCMVContext;
> +
> +static int decode_frame(AVCodecContext *avctx,
> +                        void *data, int *got_frame,
> +                        AVPacket *avpkt)
> +{
> +    WCMVContext *s = avctx->priv_data;
> +    AVFrame *frame = data;
> +    int skip, blocks, zret, ret, intra = 0;
> +    GetByteContext gb;
> +    uint8_t *dst;
> +
> +    ret = inflateReset(&s->zstream);
> +    if (ret != Z_OK) {
> +        av_log(avctx, AV_LOG_ERROR, "Inflate reset error: %d\n", ret);
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    bytestream2_init(&gb, avpkt->data, avpkt->size);
> +
> +    if ((ret = ff_get_buffer(avctx, frame, AV_GET_BUFFER_FLAG_REF)) < 0)
> +        return ret;
> +
> +    if (s->prev_frame->data[0]) {
> +        ret = av_frame_copy(frame, s->prev_frame);
> +        if (ret < 0)
> +            return ret;
> +    }
>

Shouldn't it be zero'd out if case there's no prev_frame?



> +
> +    blocks = bytestream2_get_le16(&gb);
> +    if (blocks > 5) {
> +        GetByteContext bgb;
> +        int x = 0, size;
> +
> +        if (blocks * 8 >= 0xFFFF) {
> +            size = bytestream2_get_le24(&gb);
> +        } else if (blocks * 8 >= 0xFF) {
> +            size = bytestream2_get_le16(&gb);
> +        } else {
> +            size = bytestream2_get_byte(&gb);
> +        }
>

No need for brackets here, they're all one line expressions.


+
> +        skip = bytestream2_tell(&gb);
> +        if (size > avpkt->size - skip)
> +            return AVERROR_INVALIDDATA;
> +
> +        s->zstream.next_in  = avpkt->data + skip;
> +        s->zstream.avail_in = size;
> +        s->zstream.next_out  = s->block_data;
> +        s->zstream.avail_out = sizeof(s->block_data);
> +
> +        zret = inflate(&s->zstream, Z_FINISH);
> +        if (zret != Z_STREAM_END) {
> +            av_log(avctx, AV_LOG_ERROR,
> +                   "Inflate failed with return code: %d.\n", zret);
> +            return AVERROR_INVALIDDATA;
> +        }
> +
> +        ret = inflateReset(&s->zstream);
> +        if (ret != Z_OK) {
> +            av_log(avctx, AV_LOG_ERROR, "Inflate reset error: %d\n", ret);
> +            return AVERROR_EXTERNAL;
> +        }
>

Will zlib error out if the data it tries to unpack is larger than
s->zstream.avail_out = sizeof(s->block_data)?



> +
> +        bytestream2_skip(&gb, size);
> +        bytestream2_init(&bgb, s->block_data, blocks * 8);
> +
> +        for (int i = 0; i < blocks; i++) {
> +            int w, h;
> +
> +            bytestream2_skip(&bgb, 4);
> +            w = bytestream2_get_le16(&bgb);
> +            h = bytestream2_get_le16(&bgb);
> +            x += 3 * w * h;
> +        }
> +
> +        if (x >= 0xFFFF) {
> +            bytestream2_skip(&gb, 3);
> +        } else if (x >= 0xFF) {
> +            bytestream2_skip(&gb, 2);
> +        } else {
> +            bytestream2_skip(&gb, 1);
> +        }
>

Same, oneliners so no need for brackets.


+
> +        skip = bytestream2_tell(&gb);
> +
> +        s->zstream.next_in  = avpkt->data + skip;
> +        s->zstream.avail_in = avpkt->size - skip;
> +
> +        bytestream2_init(&gb, s->block_data, blocks * 8);
> +    } else {
> +        int x = 0;
> +
> +        bytestream2_seek(&gb, 2, SEEK_SET);
> +
> +        for (int i = 0; i < blocks; i++) {
> +            int w, h;
> +
> +            bytestream2_skip(&gb, 4);
> +            w = bytestream2_get_le16(&gb);
> +            h = bytestream2_get_le16(&gb);
> +            x += 3 * w * h;
> +        }
> +
> +        if (x >= 0xFFFF) {
> +            bytestream2_skip(&gb, 3);
> +        } else if (x >= 0xFF) {
> +            bytestream2_skip(&gb, 2);
> +        } else {
> +            bytestream2_skip(&gb, 1);
> +        }
>

Same.



> +
> +        skip = bytestream2_tell(&gb);
> +
> +        s->zstream.next_in  = avpkt->data + skip;
> +        s->zstream.avail_in = avpkt->size - skip;
> +
> +        bytestream2_seek(&gb, 2, SEEK_SET);
> +    }
> +
> +    for (int block = 0; block < blocks; block++) {
> +        int x, y, w, h;
> +
> +        x = bytestream2_get_le16(&gb);
> +        y = bytestream2_get_le16(&gb);
> +        w = bytestream2_get_le16(&gb);
> +        h = bytestream2_get_le16(&gb);
> +
> +        if (blocks == 1 && x == 0 && y == 0 && w == avctx->width && h ==
> avctx->height)
> +            intra = 1;
> +
> +        if (x + w > avctx->width || y + h > avctx->height)
> +            return AVERROR_INVALIDDATA;
> +
> +        if (w > avctx->width || h > avctx->height)
> +            return AVERROR_INVALIDDATA;
> +
> +        dst = frame->data[0] + (avctx->height - y - 1) *
> frame->linesize[0] + x * 3;
> +        for (int i = 0; i < h; i++) {
> +            s->zstream.next_out  = dst;
> +            s->zstream.avail_out = w * 3;
> +
> +            zret = inflate(&s->zstream, Z_SYNC_FLUSH);
> +            if (zret != Z_OK && zret != Z_STREAM_END) {
> +                av_log(avctx, AV_LOG_ERROR,
> +                       "Inflate failed with return code: %d.\n", zret);
> +                return AVERROR_INVALIDDATA;
> +            }
> +
> +            dst -= frame->linesize[0];
> +        }
> +    }
> +
> +    frame->key_frame = intra;
> +    frame->pict_type = intra ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_P;
> +
> +    av_frame_unref(s->prev_frame);
> +    if ((ret = av_frame_ref(s->prev_frame, frame)) < 0)
> +        return ret;
> +
> +    *got_frame = 1;
> +
> +    return avpkt->size;
> +}
> +
> +static av_cold int decode_init(AVCodecContext *avctx)
> +{
> +    WCMVContext *s = avctx->priv_data;
> +    int zret;
> +
> +    avctx->pix_fmt = AV_PIX_FMT_BGR24;
> +
> +    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_EXTERNAL;
> +    }
> +
> +    s->prev_frame = av_frame_alloc();
> +    if (!s->prev_frame)
> +        return AVERROR(ENOMEM);
> +
> +    return 0;
> +}
> +
> +static av_cold int decode_close(AVCodecContext *avctx)
> +{
> +    WCMVContext *s = avctx->priv_data;
> +
> +    av_frame_free(&s->prev_frame);
> +    inflateEnd(&s->zstream);
> +
> +    return 0;
> +}
> +
> +AVCodec ff_wcmv_decoder = {
> +    .name             = "wcmv",
> +    .long_name        = NULL_IF_CONFIG_SMALL("WinCAM Motion Video"),
> +    .type             = AVMEDIA_TYPE_VIDEO,
> +    .id               = AV_CODEC_ID_WCMV,
> +    .priv_data_size   = sizeof(WCMVContext),
> +    .init             = decode_init,
> +    .close            = decode_close,
> +    .decode           = decode_frame,
> +    .capabilities     = AV_CODEC_CAP_DR1,
> +    .caps_internal    = FF_CODEC_CAP_INIT_THREADSAFE |
> +                        FF_CODEC_CAP_INIT_CLEANUP,
> +};
> diff --git a/libavformat/riff.c b/libavformat/riff.c
> index aef3c047ac..5f1aafe514 100644
> --- a/libavformat/riff.c
> +++ b/libavformat/riff.c
> @@ -473,6 +473,7 @@ const AVCodecTag ff_codec_bmp_tags[] = {
>      { AV_CODEC_ID_IMM4,         MKTAG('I', 'M', 'M', '4') },
>      { AV_CODEC_ID_PROSUMER,     MKTAG('B', 'T', '2', '0') },
>      { AV_CODEC_ID_MWSC,         MKTAG('M', 'W', 'S', 'C') },
> +    { AV_CODEC_ID_WCMV,         MKTAG('W', 'C', 'M', 'V') },
>      { AV_CODEC_ID_NONE,         0 }
>  };
>
> --
> 2.17.1
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel


lavc micro bump + line in changelog.
Michael Niedermayer Aug. 28, 2018, 12:40 a.m. UTC | #2
Hi

On Mon, Aug 27, 2018 at 07:31:21PM +0100, Rostislav Pehlivanov wrote:
> On Sat, 25 Aug 2018 at 21:12, Paul B Mahol <onemda@gmail.com> wrote:
[...]
> > +
> > +    blocks = bytestream2_get_le16(&gb);
> > +    if (blocks > 5) {
> > +        GetByteContext bgb;
> > +        int x = 0, size;
> > +
> > +        if (blocks * 8 >= 0xFFFF) {
> > +            size = bytestream2_get_le24(&gb);
> > +        } else if (blocks * 8 >= 0xFF) {
> > +            size = bytestream2_get_le16(&gb);
> > +        } else {
> > +            size = bytestream2_get_byte(&gb);
> > +        }
> >
> 
> No need for brackets here, they're all one line expressions.

You are correct. The extra brackets simplify any future additions to
the branches though (which was why this style was often prefered long ago)
It may not apply here, but for example what the {} do to changes is that:

        if (blocks * 8 >= 0xFFFF) {
            size = bytestream2_get_le24(&gb);
+           if (size == whatever)
+               return AVERROR_INVALIDDATA;
        } else if (blocks * 8 >= 0xFF) {
            size = bytestream2_get_le16(&gb);
        } else {
            size = bytestream2_get_byte(&gb);
        }

vs:

-       if (blocks * 8 >= 0xFFFF)
+       if (blocks * 8 >= 0xFFFF) {
            size = bytestream2_get_le24(&gb);
-       else if (blocks * 8 >= 0xFF)
+           if (size == whatever)
+               return AVERROR_INVALIDDATA;
+       } else if (blocks * 8 >= 0xFF)
            size = bytestream2_get_le16(&gb);
        else
            size = bytestream2_get_byte(&gb);

            
The first diff is easier & quicker to read, aka it helps with future
maintaince
also, extra {} often cost no vertical space

I just now realized you often recommand to remove {}, which is why i
wrote above. This is not meant as opposition for this specific change,
just wanted to explain the reasoning that iam aware of behind the use of {}
in cases that seemingly dont benefit from them.



[...]
> 
> 
> > +
> > +        bytestream2_skip(&gb, size);
> > +        bytestream2_init(&bgb, s->block_data, blocks * 8);
> > +
> > +        for (int i = 0; i < blocks; i++) {
> > +            int w, h;
> > +
> > +            bytestream2_skip(&bgb, 4);
> > +            w = bytestream2_get_le16(&bgb);
> > +            h = bytestream2_get_le16(&bgb);
> > +            x += 3 * w * h;
> > +        }
> > +

> > +        if (x >= 0xFFFF) {
> > +            bytestream2_skip(&gb, 3);
> > +        } else if (x >= 0xFF) {
> > +            bytestream2_skip(&gb, 2);
> > +        } else {
> > +            bytestream2_skip(&gb, 1);
> > +        }
> >
> 
> Same, oneliners so no need for brackets.
> 
> 
> +
> > +        skip = bytestream2_tell(&gb);
> > +
> > +        s->zstream.next_in  = avpkt->data + skip;
> > +        s->zstream.avail_in = avpkt->size - skip;
> > +
> > +        bytestream2_init(&gb, s->block_data, blocks * 8);
> > +    } else {
> > +        int x = 0;
> > +
> > +        bytestream2_seek(&gb, 2, SEEK_SET);
> > +
> > +        for (int i = 0; i < blocks; i++) {
> > +            int w, h;
> > +
> > +            bytestream2_skip(&gb, 4);
> > +            w = bytestream2_get_le16(&gb);
> > +            h = bytestream2_get_le16(&gb);
> > +            x += 3 * w * h;
> > +        }
> > +
> > +        if (x >= 0xFFFF) {
> > +            bytestream2_skip(&gb, 3);
> > +        } else if (x >= 0xFF) {
> > +            bytestream2_skip(&gb, 2);
> > +        } else {
> > +            bytestream2_skip(&gb, 1);
> > +        }
> >
> 
> Same.
> 
> 
> 
> > +
> > +        skip = bytestream2_tell(&gb);
> > +
> > +        s->zstream.next_in  = avpkt->data + skip;
> > +        s->zstream.avail_in = avpkt->size - skip;

This code is identical, and could maybe be factored out

[...]
diff mbox

Patch

diff --git a/configure b/configure
index 0c1f6a79a7..469797935e 100755
--- a/configure
+++ b/configure
@@ -2750,6 +2750,7 @@  vp6f_decoder_select="vp6_decoder"
 vp7_decoder_select="h264pred videodsp vp8dsp"
 vp8_decoder_select="h264pred videodsp vp8dsp"
 vp9_decoder_select="videodsp vp9_parser vp9_superframe_split_bsf"
+wcmv_decoder_deps="zlib"
 webp_decoder_select="vp8_decoder exif"
 wmalossless_decoder_select="llauddsp"
 wmapro_decoder_select="mdct sinewin wma_freqs"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index aee4f5431a..f8673f0121 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -677,6 +677,7 @@  OBJS-$(CONFIG_VP9_V4L2M2M_DECODER)     += v4l2_m2m_dec.o
 OBJS-$(CONFIG_VQA_DECODER)             += vqavideo.o
 OBJS-$(CONFIG_WAVPACK_DECODER)         += wavpack.o
 OBJS-$(CONFIG_WAVPACK_ENCODER)         += wavpackenc.o
+OBJS-$(CONFIG_WCMV_DECODER)            += wcmv.o
 OBJS-$(CONFIG_WEBP_DECODER)            += webp.o
 OBJS-$(CONFIG_WEBVTT_DECODER)          += webvttdec.o ass.o
 OBJS-$(CONFIG_WEBVTT_ENCODER)          += webvttenc.o ass_split.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index d41868c591..42d98f9b0a 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -338,6 +338,7 @@  extern AVCodec ff_vp9_v4l2m2m_decoder;
 extern AVCodec ff_vqa_decoder;
 extern AVCodec ff_bitpacked_decoder;
 extern AVCodec ff_webp_decoder;
+extern AVCodec ff_wcmv_decoder;
 extern AVCodec ff_wrapped_avframe_encoder;
 extern AVCodec ff_wrapped_avframe_decoder;
 extern AVCodec ff_wmv1_encoder;
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index be41b8cf0e..b6688b7af3 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -450,6 +450,7 @@  enum AVCodecID {
     AV_CODEC_ID_IMM4,
     AV_CODEC_ID_PROSUMER,
     AV_CODEC_ID_MWSC,
+    AV_CODEC_ID_WCMV,
 
     /* 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 129d0f1aac..46dfe3f5e5 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -1675,6 +1675,13 @@  static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("MatchWare Screen Capture Codec"),
         .props     = AV_CODEC_PROP_LOSSLESS,
     },
+    {
+        .id        = AV_CODEC_ID_WCMV,
+        .type      = AVMEDIA_TYPE_VIDEO,
+        .name      = "wcmv",
+        .long_name = NULL_IF_CONFIG_SMALL("WinCAM Motion Video"),
+        .props     = AV_CODEC_PROP_LOSSLESS,
+    },
 
     /* various PCM "codecs" */
     {
diff --git a/libavcodec/wcmv.c b/libavcodec/wcmv.c
new file mode 100644
index 0000000000..74cb1166bb
--- /dev/null
+++ b/libavcodec/wcmv.c
@@ -0,0 +1,247 @@ 
+/*
+ * WinCAM Motion Video decoder
+ *
+ * Copyright (c) 2018 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 WCMVContext {
+    z_stream    zstream;
+    AVFrame    *prev_frame;
+    uint8_t     block_data[65536*8];
+} WCMVContext;
+
+static int decode_frame(AVCodecContext *avctx,
+                        void *data, int *got_frame,
+                        AVPacket *avpkt)
+{
+    WCMVContext *s = avctx->priv_data;
+    AVFrame *frame = data;
+    int skip, blocks, zret, ret, intra = 0;
+    GetByteContext gb;
+    uint8_t *dst;
+
+    ret = inflateReset(&s->zstream);
+    if (ret != Z_OK) {
+        av_log(avctx, AV_LOG_ERROR, "Inflate reset error: %d\n", ret);
+        return AVERROR_EXTERNAL;
+    }
+
+    bytestream2_init(&gb, avpkt->data, avpkt->size);
+
+    if ((ret = ff_get_buffer(avctx, frame, AV_GET_BUFFER_FLAG_REF)) < 0)
+        return ret;
+
+    if (s->prev_frame->data[0]) {
+        ret = av_frame_copy(frame, s->prev_frame);
+        if (ret < 0)
+            return ret;
+    }
+
+    blocks = bytestream2_get_le16(&gb);
+    if (blocks > 5) {
+        GetByteContext bgb;
+        int x = 0, size;
+
+        if (blocks * 8 >= 0xFFFF) {
+            size = bytestream2_get_le24(&gb);
+        } else if (blocks * 8 >= 0xFF) {
+            size = bytestream2_get_le16(&gb);
+        } else {
+            size = bytestream2_get_byte(&gb);
+        }
+
+        skip = bytestream2_tell(&gb);
+        if (size > avpkt->size - skip)
+            return AVERROR_INVALIDDATA;
+
+        s->zstream.next_in  = avpkt->data + skip;
+        s->zstream.avail_in = size;
+        s->zstream.next_out  = s->block_data;
+        s->zstream.avail_out = sizeof(s->block_data);
+
+        zret = inflate(&s->zstream, Z_FINISH);
+        if (zret != Z_STREAM_END) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Inflate failed with return code: %d.\n", zret);
+            return AVERROR_INVALIDDATA;
+        }
+
+        ret = inflateReset(&s->zstream);
+        if (ret != Z_OK) {
+            av_log(avctx, AV_LOG_ERROR, "Inflate reset error: %d\n", ret);
+            return AVERROR_EXTERNAL;
+        }
+
+        bytestream2_skip(&gb, size);
+        bytestream2_init(&bgb, s->block_data, blocks * 8);
+
+        for (int i = 0; i < blocks; i++) {
+            int w, h;
+
+            bytestream2_skip(&bgb, 4);
+            w = bytestream2_get_le16(&bgb);
+            h = bytestream2_get_le16(&bgb);
+            x += 3 * w * h;
+        }
+
+        if (x >= 0xFFFF) {
+            bytestream2_skip(&gb, 3);
+        } else if (x >= 0xFF) {
+            bytestream2_skip(&gb, 2);
+        } else {
+            bytestream2_skip(&gb, 1);
+        }
+
+        skip = bytestream2_tell(&gb);
+
+        s->zstream.next_in  = avpkt->data + skip;
+        s->zstream.avail_in = avpkt->size - skip;
+
+        bytestream2_init(&gb, s->block_data, blocks * 8);
+    } else {
+        int x = 0;
+
+        bytestream2_seek(&gb, 2, SEEK_SET);
+
+        for (int i = 0; i < blocks; i++) {
+            int w, h;
+
+            bytestream2_skip(&gb, 4);
+            w = bytestream2_get_le16(&gb);
+            h = bytestream2_get_le16(&gb);
+            x += 3 * w * h;
+        }
+
+        if (x >= 0xFFFF) {
+            bytestream2_skip(&gb, 3);
+        } else if (x >= 0xFF) {
+            bytestream2_skip(&gb, 2);
+        } else {
+            bytestream2_skip(&gb, 1);
+        }
+
+        skip = bytestream2_tell(&gb);
+
+        s->zstream.next_in  = avpkt->data + skip;
+        s->zstream.avail_in = avpkt->size - skip;
+
+        bytestream2_seek(&gb, 2, SEEK_SET);
+    }
+
+    for (int block = 0; block < blocks; block++) {
+        int x, y, w, h;
+
+        x = bytestream2_get_le16(&gb);
+        y = bytestream2_get_le16(&gb);
+        w = bytestream2_get_le16(&gb);
+        h = bytestream2_get_le16(&gb);
+
+        if (blocks == 1 && x == 0 && y == 0 && w == avctx->width && h == avctx->height)
+            intra = 1;
+
+        if (x + w > avctx->width || y + h > avctx->height)
+            return AVERROR_INVALIDDATA;
+
+        if (w > avctx->width || h > avctx->height)
+            return AVERROR_INVALIDDATA;
+
+        dst = frame->data[0] + (avctx->height - y - 1) * frame->linesize[0] + x * 3;
+        for (int i = 0; i < h; i++) {
+            s->zstream.next_out  = dst;
+            s->zstream.avail_out = w * 3;
+
+            zret = inflate(&s->zstream, Z_SYNC_FLUSH);
+            if (zret != Z_OK && zret != Z_STREAM_END) {
+                av_log(avctx, AV_LOG_ERROR,
+                       "Inflate failed with return code: %d.\n", zret);
+                return AVERROR_INVALIDDATA;
+            }
+
+            dst -= frame->linesize[0];
+        }
+    }
+
+    frame->key_frame = intra;
+    frame->pict_type = intra ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_P;
+
+    av_frame_unref(s->prev_frame);
+    if ((ret = av_frame_ref(s->prev_frame, frame)) < 0)
+        return ret;
+
+    *got_frame = 1;
+
+    return avpkt->size;
+}
+
+static av_cold int decode_init(AVCodecContext *avctx)
+{
+    WCMVContext *s = avctx->priv_data;
+    int zret;
+
+    avctx->pix_fmt = AV_PIX_FMT_BGR24;
+
+    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_EXTERNAL;
+    }
+
+    s->prev_frame = av_frame_alloc();
+    if (!s->prev_frame)
+        return AVERROR(ENOMEM);
+
+    return 0;
+}
+
+static av_cold int decode_close(AVCodecContext *avctx)
+{
+    WCMVContext *s = avctx->priv_data;
+
+    av_frame_free(&s->prev_frame);
+    inflateEnd(&s->zstream);
+
+    return 0;
+}
+
+AVCodec ff_wcmv_decoder = {
+    .name             = "wcmv",
+    .long_name        = NULL_IF_CONFIG_SMALL("WinCAM Motion Video"),
+    .type             = AVMEDIA_TYPE_VIDEO,
+    .id               = AV_CODEC_ID_WCMV,
+    .priv_data_size   = sizeof(WCMVContext),
+    .init             = decode_init,
+    .close            = decode_close,
+    .decode           = decode_frame,
+    .capabilities     = AV_CODEC_CAP_DR1,
+    .caps_internal    = FF_CODEC_CAP_INIT_THREADSAFE |
+                        FF_CODEC_CAP_INIT_CLEANUP,
+};
diff --git a/libavformat/riff.c b/libavformat/riff.c
index aef3c047ac..5f1aafe514 100644
--- a/libavformat/riff.c
+++ b/libavformat/riff.c
@@ -473,6 +473,7 @@  const AVCodecTag ff_codec_bmp_tags[] = {
     { AV_CODEC_ID_IMM4,         MKTAG('I', 'M', 'M', '4') },
     { AV_CODEC_ID_PROSUMER,     MKTAG('B', 'T', '2', '0') },
     { AV_CODEC_ID_MWSC,         MKTAG('M', 'W', 'S', 'C') },
+    { AV_CODEC_ID_WCMV,         MKTAG('W', 'C', 'M', 'V') },
     { AV_CODEC_ID_NONE,         0 }
 };