From e8a45ce4748e395e829c664c61b57e3c2e9d9fc8 Mon Sep 17 00:00:00 2001
From: Hein-Pieter van Braam <hp@tmm.cx>
Date: Fri, 16 Jun 2017 22:02:43 +0200
Subject: [PATCH] Implement support for interplay MVE 0x06 and 0x10

---
 libavcodec/interplayvideo.c | 276 +++++++++++++++++++++++++++++++++++++++++---
 libavformat/ipmovie.c       | 101 ++++++++++++----
 2 files changed, 338 insertions(+), 39 deletions(-)

diff --git a/libavcodec/interplayvideo.c b/libavcodec/interplayvideo.c
index df3314d..d8c6543 100644
--- a/libavcodec/interplayvideo.c
+++ b/libavcodec/interplayvideo.c
@@ -55,11 +55,21 @@ typedef struct IpvideoContext {
     HpelDSPContext hdsp;
     AVFrame *second_last_frame;
     AVFrame *last_frame;
+
+    /* For format 0x10 */
+    AVFrame *cur_decode_frame;
+    AVFrame *prev_decode_frame;
+
+    uint8_t frame_format;
+    const unsigned char *skip_map;
+    int skip_map_size;
     const unsigned char *decoding_map;
     int decoding_map_size;
+    int video_data_size;
 
     int is_16bpp;
     GetByteContext stream_ptr, mv_ptr;
+    GetByteContext decoding_map_ptr, skip_map_ptr;
     unsigned char *pixel_ptr;
     int line_inc;
     int stride;
@@ -903,7 +913,182 @@ static int (* const ipvideo_decode_block16[])(IpvideoContext *s, AVFrame *frame)
     ipvideo_decode_block_opcode_0xE_16, ipvideo_decode_block_opcode_0x1,
 };
 
-static void ipvideo_decode_opcodes(IpvideoContext *s, AVFrame *frame)
+
+static void ipvideo_decode_format_06_opcodes(IpvideoContext *s, AVFrame *frame)
+{
+    int x, y, off_x, off_y;
+    short opcode;
+
+    if (!s->is_16bpp) {
+        /* this is PAL8, so make the palette available */
+        memcpy(frame->data[1], s->pal, AVPALETTE_SIZE);
+
+        s->stride = frame->linesize[0];
+    }
+
+    s->line_inc = s->stride - 8;
+    s->upper_motion_limit_offset = (s->avctx->height - 8) * frame->linesize[0]
+                                  + (s->avctx->width - 8) * (1 + s->is_16bpp);
+
+    for (y = 0; y < s->avctx->height; y += 8) {
+        for (x = 0; x < s->avctx->width; x += 8) {
+            opcode = bytestream2_get_le16(&s->decoding_map_ptr);
+
+            ff_tlog(s->avctx,
+                    "  block @ (%3d, %3d): opcode 0x%X, data ptr offset %d\n",
+                    x, y, opcode, bytestream2_tell(&s->stream_ptr));
+
+            s->pixel_ptr = frame->data[0] + x + y*frame->linesize[0];
+            if (! opcode) {
+                int k;
+                for (k = 0; k < 8; k++) {
+                    bytestream2_get_buffer(&s->stream_ptr, s->pixel_ptr, 8);
+                    s->pixel_ptr += s->stride;
+                }
+            } else {
+                /* Don't try to copy second_last_frame data on the first frame */
+                if (s->avctx->frame_number > 2)
+                    copy_from(s, s->second_last_frame, frame, 0, 0);
+            }
+        }
+    }
+
+    bytestream2_seek(&s->decoding_map_ptr, 0, SEEK_SET);
+    for (y = 0; y < s->avctx->height; y += 8) {
+        for (x = 0; x < s->avctx->width; x += 8) {
+            opcode = bytestream2_get_le16(&s->decoding_map_ptr);
+
+            ff_tlog(s->avctx,
+                    "  block @ (%3d, %3d): opcode 0x%X, data ptr offset %d\n",
+                    x, y, opcode, bytestream2_tell(&s->stream_ptr));
+
+            s->pixel_ptr = frame->data[0] + x + y*frame->linesize[0];
+            if (opcode < 0) {
+                off_x = ((unsigned short)opcode - 0xC000) % frame->linesize[0];
+                off_y = ((unsigned short)opcode - 0xC000) / frame->linesize[0];
+                copy_from(s, s->last_frame, frame, off_x, off_y);
+            }
+            if (opcode > 0) {
+                off_x = ((unsigned short)opcode - 0x4000) % frame->linesize[0];
+                off_y = ((unsigned short)opcode - 0x4000) / frame->linesize[0];
+                copy_from(s, frame, frame, off_x, off_y);
+            }
+        }
+    }
+
+    if (bytestream2_get_bytes_left(&s->stream_ptr) > 1) {
+        av_log(s->avctx, AV_LOG_DEBUG,
+               "decode finished with %d bytes left over\n",
+               bytestream2_get_bytes_left(&s->stream_ptr));
+    }
+}
+
+static void ipvideo_decode_format_10_opcodes(IpvideoContext *s, AVFrame *frame)
+{
+    int x, y, off_x, off_y;
+    short opcode, skip;
+    int changed_block = 0;
+
+    if (!s->is_16bpp) {
+        /* this is PAL8, so make the palette available */
+        memcpy(frame->data[1], s->pal, AVPALETTE_SIZE);
+
+        s->stride = frame->linesize[0];
+    }
+
+    bytestream2_skip(&s->stream_ptr, 14); /* data starts 14 bytes in */
+
+    s->line_inc = s->stride - 8;
+    s->upper_motion_limit_offset = (s->avctx->height - 8) * frame->linesize[0]
+                                  + (s->avctx->width - 8) * (1 + s->is_16bpp);
+
+    skip = bytestream2_get_le16(&s->skip_map_ptr);
+    for (y = 0; y < s->avctx->height; y += 8) {
+        for (x = 0; x < s->avctx->width; x += 8) {
+            s->pixel_ptr = s->cur_decode_frame->data[0] + x + y*s->cur_decode_frame->linesize[0];
+
+            while (skip <= 0)  {
+                if (skip != -0x8000 && skip) {
+                    opcode = bytestream2_get_le16(&s->decoding_map_ptr);
+                    if (! opcode) {
+                        int k;
+                        for (k = 0; k < 8; k++) {
+                            bytestream2_get_buffer(&s->stream_ptr, s->pixel_ptr, 8);
+                            s->pixel_ptr += s->stride;
+                        }
+                    }
+                    break;
+                }
+                skip = bytestream2_get_le16(&s->skip_map_ptr);
+            }
+            skip *= 2;
+        }
+    }
+
+    bytestream2_seek(&s->decoding_map_ptr, 0, SEEK_SET);
+    bytestream2_seek(&s->skip_map_ptr, 0, SEEK_SET);
+    skip = bytestream2_get_le16(&s->skip_map_ptr);
+    for (y = 0; y < s->avctx->height; y += 8) {
+        for (x = 0; x < s->avctx->width; x += 8) {
+            s->pixel_ptr = s->cur_decode_frame->data[0] + x + y*s->cur_decode_frame->linesize[0];
+
+            while (skip <= 0)  {
+                if (skip != -0x8000 && skip) {
+                    opcode = bytestream2_get_le16(&s->decoding_map_ptr);
+                    if (opcode < 0) {
+                        off_x = ((unsigned short)opcode - 0xC000) % s->cur_decode_frame->linesize[0];
+                        off_y = ((unsigned short)opcode - 0xC000) / s->cur_decode_frame->linesize[0];
+                        copy_from(s, s->prev_decode_frame, s->cur_decode_frame, off_x, off_y);
+                    }
+                    if (opcode > 0) {
+                        off_x = ((unsigned short)opcode - 0x4000) % s->cur_decode_frame->linesize[0];
+                        off_y = ((unsigned short)opcode - 0x4000) / s->cur_decode_frame->linesize[0];
+                        copy_from(s, s->cur_decode_frame, s->cur_decode_frame, off_x, off_y);
+                    }
+                    break;
+                }
+                skip = bytestream2_get_le16(&s->skip_map_ptr);
+            }
+            skip *= 2;
+        }
+    }
+
+    bytestream2_seek(&s->skip_map_ptr, 0, SEEK_SET);
+    skip = bytestream2_get_le16(&s->skip_map_ptr);
+    for (y = 0; y < s->avctx->height; y += 8) {
+        for (x = 0; x < s->avctx->width; x += 8) {
+            changed_block = 0;
+            s->pixel_ptr = frame->data[0] + x + y*frame->linesize[0];
+
+            while (skip <= 0)  {
+                if (skip != -0x8000 && skip) {
+                    changed_block = 1;
+                    break;
+                }
+                skip = bytestream2_get_le16(&s->skip_map_ptr);
+            }
+
+            if (changed_block) {
+                copy_from(s, s->cur_decode_frame, frame, 0, 0);
+            } else {
+                /* Don't try to copy last_frame data on the first frame */
+                if (s->avctx->frame_number)
+                    copy_from(s, s->last_frame, frame, 0, 0);
+            }
+            skip *= 2;
+        }
+    }
+
+    FFSWAP(AVFrame*, s->prev_decode_frame, s->cur_decode_frame);
+
+    if (bytestream2_get_bytes_left(&s->stream_ptr) > 1) {
+        av_log(s->avctx, AV_LOG_DEBUG,
+               "decode finished with %d bytes left over\n",
+               bytestream2_get_bytes_left(&s->stream_ptr));
+    }
+}
+
+static void ipvideo_decode_format_11_opcodes(IpvideoContext *s, AVFrame *frame)
 {
     int x, y;
     unsigned char opcode;
@@ -972,12 +1157,26 @@ static av_cold int ipvideo_decode_init(AVCodecContext *avctx)
 
     s->last_frame        = av_frame_alloc();
     s->second_last_frame = av_frame_alloc();
-    if (!s->last_frame || !s->second_last_frame) {
+    s->cur_decode_frame  = av_frame_alloc();
+    s->prev_decode_frame = av_frame_alloc();
+    if (!s->last_frame || !s->second_last_frame ||
+        !s->cur_decode_frame || !s->prev_decode_frame) {
         av_frame_free(&s->last_frame);
         av_frame_free(&s->second_last_frame);
+        av_frame_free(&s->cur_decode_frame);
+        av_frame_free(&s->prev_decode_frame);
         return AVERROR(ENOMEM);
     }
 
+    s->cur_decode_frame->width   = avctx->width;
+    s->prev_decode_frame->width  = avctx->width;
+    s->cur_decode_frame->height  = avctx->height;
+    s->prev_decode_frame->height = avctx->height;
+    s->cur_decode_frame->format  = avctx->pix_fmt;
+    s->prev_decode_frame->format = avctx->pix_fmt;
+
+    ff_get_buffer(avctx, s->cur_decode_frame, 0);
+    ff_get_buffer(avctx, s->prev_decode_frame, 0);
     return 0;
 }
 
@@ -999,18 +1198,56 @@ static int ipvideo_decode_frame(AVCodecContext *avctx,
     if (buf_size < 2)
         return AVERROR_INVALIDDATA;
 
-    /* decoding map contains 4 bits of information per 8x8 block */
-    s->decoding_map_size = AV_RL16(avpkt->data);
-
-    /* compressed buffer needs to be large enough to at least hold an entire
-     * decoding map */
-    if (buf_size < s->decoding_map_size + 2)
-        return buf_size;
-
-
-    s->decoding_map = buf + 2;
-    bytestream2_init(&s->stream_ptr, buf + 2 + s->decoding_map_size,
-                     buf_size - s->decoding_map_size);
+    s->frame_format = AV_RB8(avpkt->data);
+    switch (s->frame_format) {
+        case 0x06:
+            /* Format 0x06 has decoding map appended to the top of pixel data */
+            /* decoding map contains 16 bits of information per 8x8 block */
+            s->decoding_map_size = ((s->avctx->width / 8) * (s->avctx->height / 8)) * 2;
+            s->decoding_map = buf + 3 + 14; /* 14 bits of op data */
+
+            s->video_data_size = AV_RL16(avpkt->data + 1) - s->decoding_map_size - 14;
+            bytestream2_init(&s->stream_ptr, buf + 3 + s->decoding_map_size + 14, s->video_data_size);
+            bytestream2_init(&s->decoding_map_ptr, s->decoding_map, s->decoding_map_size);
+
+            if (buf_size < s->decoding_map_size + 2)
+                return buf_size;
+            break;
+
+        case 0x10:
+            /* Format 0x10 has a decoding map, pixel data, and a skip map */
+            s->video_data_size = AV_RL16(avpkt->data + 1);
+            bytestream2_init(&s->stream_ptr, buf + 3, s->video_data_size);
+
+            /* decoding map contains 16 bits of information per 8x8 block */
+            s->decoding_map_size = AV_RL16(avpkt->data + 3 + s->video_data_size);
+            s->decoding_map = buf + 3 + s->video_data_size + 2;
+            bytestream2_init(&s->decoding_map_ptr, s->decoding_map, s->decoding_map_size);
+
+            /* skip map contains 16 bits of information per 15 blocks, ish */
+            s->skip_map_size = AV_RL16(avpkt->data + 3 + s->video_data_size + 2 + s->decoding_map_size);
+            s->skip_map = buf + 3 + s->video_data_size + 2 + s->decoding_map_size + 2;
+            bytestream2_init(&s->skip_map_ptr, s->skip_map, s->skip_map_size);
+            break;
+
+        case 0x11:
+            /* Format 0x11 has a decoding map and pixel data */
+            s->video_data_size = AV_RL16(avpkt->data + 1);
+            bytestream2_init(&s->stream_ptr, buf + 3, s->video_data_size);
+
+            /* decoding map contains 4 bits of information per 8x8 block */
+            s->decoding_map_size = AV_RL16(avpkt->data + 3 + s->video_data_size);
+            s->decoding_map = buf + 3 + s->video_data_size + 2;
+
+            /* compressed buffer needs to be large enough to at least hold an entire
+             * decoding map */
+            if (buf_size < s->decoding_map_size + 2)
+                return buf_size;
+            break;
+
+        default:
+            av_log(avctx, AV_LOG_ERROR, "Frame type 0x%02X unsupported\n", s->frame_format);
+    }
 
     if ((ret = ff_get_buffer(avctx, frame, AV_GET_BUFFER_FLAG_REF)) < 0)
         return ret;
@@ -1026,7 +1263,14 @@ static int ipvideo_decode_frame(AVCodecContext *avctx,
         }
     }
 
-    ipvideo_decode_opcodes(s, frame);
+    if (s->frame_format == 0x06)
+        ipvideo_decode_format_06_opcodes(s, frame);
+
+    if (s->frame_format == 0x10)
+        ipvideo_decode_format_10_opcodes(s, frame);
+
+    if (s->frame_format == 0x11)
+        ipvideo_decode_format_11_opcodes(s, frame);
 
     *got_frame = 1;
 
@@ -1046,6 +1290,8 @@ static av_cold int ipvideo_decode_end(AVCodecContext *avctx)
 
     av_frame_free(&s->last_frame);
     av_frame_free(&s->second_last_frame);
+    av_frame_free(&s->cur_decode_frame);
+    av_frame_free(&s->prev_decode_frame);
 
     return 0;
 }
diff --git a/libavformat/ipmovie.c b/libavformat/ipmovie.c
index a83909f..3c4d85c 100644
--- a/libavformat/ipmovie.c
+++ b/libavformat/ipmovie.c
@@ -58,7 +58,7 @@
 #define OPCODE_INIT_AUDIO_BUFFERS      0x03
 #define OPCODE_START_STOP_AUDIO        0x04
 #define OPCODE_INIT_VIDEO_BUFFERS      0x05
-#define OPCODE_UNKNOWN_06              0x06
+#define OPCODE_VIDEO_DATA_06           0x06
 #define OPCODE_SEND_BUFFER             0x07
 #define OPCODE_AUDIO_FRAME             0x08
 #define OPCODE_SILENCE_FRAME           0x09
@@ -66,10 +66,10 @@
 #define OPCODE_CREATE_GRADIENT         0x0B
 #define OPCODE_SET_PALETTE             0x0C
 #define OPCODE_SET_PALETTE_COMPRESSED  0x0D
-#define OPCODE_UNKNOWN_0E              0x0E
+#define OPCODE_SET_SKIP_MAP            0x0E
 #define OPCODE_SET_DECODING_MAP        0x0F
-#define OPCODE_UNKNOWN_10              0x10
-#define OPCODE_VIDEO_DATA              0x11
+#define OPCODE_VIDEO_DATA_10           0x10
+#define OPCODE_VIDEO_DATA_11           0x11
 #define OPCODE_UNKNOWN_12              0x12
 #define OPCODE_UNKNOWN_13              0x13
 #define OPCODE_UNKNOWN_14              0x14
@@ -91,6 +91,7 @@ typedef struct IPMVEContext {
     uint32_t     palette[256];
     int          has_palette;
     int          changed;
+    uint8_t      frame_format;
 
     unsigned int audio_bits;
     unsigned int audio_channels;
@@ -105,6 +106,8 @@ typedef struct IPMVEContext {
     int audio_chunk_size;
     int64_t video_chunk_offset;
     int video_chunk_size;
+    int64_t skip_map_chunk_offset;
+    int skip_map_chunk_size;
     int64_t decode_map_chunk_offset;
     int decode_map_chunk_size;
 
@@ -152,11 +155,11 @@ static int load_ipmovie_packet(IPMVEContext *s, AVIOContext *pb,
 
         chunk_type = CHUNK_VIDEO;
 
-    } else if (s->decode_map_chunk_offset) {
+    } else if (s->frame_format) {
+        av_log(s->avf, AV_LOG_TRACE, "frame format 0x%02X\n", s->frame_format);
 
-        /* send both the decode map and the video data together */
-
-        if (av_new_packet(pkt, 2 + s->decode_map_chunk_size + s->video_chunk_size))
+        /* send video data, skip map, and decode map together */
+        if (av_new_packet(pkt, 1 + 2 + s->video_chunk_size + 2 + s->decode_map_chunk_size + 2 + s->skip_map_chunk_size))
             return CHUNK_NOMEM;
 
         if (s->has_palette) {
@@ -174,26 +177,47 @@ static int load_ipmovie_packet(IPMVEContext *s, AVIOContext *pb,
             ff_add_param_change(pkt, 0, 0, 0, s->video_width, s->video_height);
             s->changed = 0;
         }
-        pkt->pos= s->decode_map_chunk_offset;
-        avio_seek(pb, s->decode_map_chunk_offset, SEEK_SET);
-        s->decode_map_chunk_offset = 0;
 
-        AV_WL16(pkt->data, s->decode_map_chunk_size);
-        if (avio_read(pb, pkt->data + 2, s->decode_map_chunk_size) !=
-            s->decode_map_chunk_size) {
-            av_packet_unref(pkt);
-            return CHUNK_EOF;
-        }
+        AV_WB8(pkt->data, s->frame_format);
+        s->frame_format = 0;
 
+        pkt->pos= s->video_chunk_offset;
         avio_seek(pb, s->video_chunk_offset, SEEK_SET);
         s->video_chunk_offset = 0;
 
-        if (avio_read(pb, pkt->data + 2 + s->decode_map_chunk_size,
-            s->video_chunk_size) != s->video_chunk_size) {
+        AV_WL16(pkt->data + 1, s->video_chunk_size);
+        if (avio_read(pb, pkt->data + 3, s->video_chunk_size) !=
+            s->video_chunk_size) {
             av_packet_unref(pkt);
             return CHUNK_EOF;
         }
 
+        if (s->decode_map_chunk_size) {
+            pkt->pos= s->decode_map_chunk_offset;
+            avio_seek(pb, s->decode_map_chunk_offset, SEEK_SET);
+            s->decode_map_chunk_offset = 0;
+
+            AV_WL16(pkt->data + 3 + s->video_chunk_size, s->decode_map_chunk_size);
+            if (avio_read(pb, pkt->data + 3 + s->video_chunk_size + 2, s->decode_map_chunk_size) !=
+                s->decode_map_chunk_size) {
+                av_packet_unref(pkt);
+                return CHUNK_EOF;
+            }
+        }
+
+        if (s->skip_map_chunk_size) {
+            pkt->pos= s->skip_map_chunk_offset;
+            avio_seek(pb, s->skip_map_chunk_offset, SEEK_SET);
+            s->skip_map_chunk_offset = 0;
+
+            AV_WL16(pkt->data + 3 + s->video_chunk_size + 2 + s->decode_map_chunk_size, s->skip_map_chunk_size);
+            if (avio_read(pb, pkt->data + 3 + s->video_chunk_size + 2 + s->decode_map_chunk_size + 2, s->skip_map_chunk_size) !=
+                s->skip_map_chunk_size) {
+                av_packet_unref(pkt);
+                return CHUNK_EOF;
+            }
+        }
+
         pkt->stream_index = s->video_stream_index;
         pkt->pts = s->video_pts;
 
@@ -430,9 +454,6 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb,
                     s->video_width, s->video_height);
             break;
 
-        case OPCODE_UNKNOWN_06:
-        case OPCODE_UNKNOWN_0E:
-        case OPCODE_UNKNOWN_10:
         case OPCODE_UNKNOWN_12:
         case OPCODE_UNKNOWN_13:
         case OPCODE_UNKNOWN_14:
@@ -513,6 +534,15 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb,
             avio_skip(pb, opcode_size);
             break;
 
+        case OPCODE_SET_SKIP_MAP:
+            av_log(s->avf, AV_LOG_TRACE, "set skip map\n");
+
+            /* log position and move on for now */
+            s->skip_map_chunk_offset = avio_tell(pb);
+            s->skip_map_chunk_size = opcode_size;
+            avio_skip(pb, opcode_size);
+            break;
+
         case OPCODE_SET_DECODING_MAP:
             av_log(s->avf, AV_LOG_TRACE, "set decoding map\n");
 
@@ -522,8 +552,29 @@ static int process_ipmovie_chunk(IPMVEContext *s, AVIOContext *pb,
             avio_skip(pb, opcode_size);
             break;
 
-        case OPCODE_VIDEO_DATA:
-            av_log(s->avf, AV_LOG_TRACE, "set video data\n");
+        case OPCODE_VIDEO_DATA_06:
+            av_log(s->avf, AV_LOG_TRACE, "set video data type 0x06\n");
+            s->frame_format = 0x06;
+
+            /* log position and move on for now */
+            s->video_chunk_offset = avio_tell(pb);
+            s->video_chunk_size = opcode_size;
+            avio_skip(pb, opcode_size);
+            break;
+
+        case OPCODE_VIDEO_DATA_10:
+            av_log(s->avf, AV_LOG_TRACE, "set video data type 0x10\n");
+            s->frame_format = 0x10;
+
+            /* log position and move on for now */
+            s->video_chunk_offset = avio_tell(pb);
+            s->video_chunk_size = opcode_size;
+            avio_skip(pb, opcode_size);
+            break;
+
+        case OPCODE_VIDEO_DATA_11:
+            av_log(s->avf, AV_LOG_TRACE, "set video data type 0x11\n");
+            s->frame_format = 0x11;
 
             /* log position and move on for now */
             s->video_chunk_offset = avio_tell(pb);
@@ -587,8 +638,10 @@ static int ipmovie_read_header(AVFormatContext *s)
             return AVERROR_EOF;
     }
     /* initialize private context members */
+    ipmovie->frame_format = 0;
     ipmovie->video_pts = ipmovie->audio_frame_count = 0;
     ipmovie->audio_chunk_offset = ipmovie->video_chunk_offset =
+    ipmovie->skip_map_chunk_offset = 0;
     ipmovie->decode_map_chunk_offset = 0;
 
     /* on the first read, this will position the stream at the first chunk */
-- 
2.9.4

