diff mbox

[FFmpeg-devel,RFC] Implement support for interplay MVE 0x06, 0x0F and 0x10

Message ID 1497704913.3919.12.camel@tmm.cx
State Superseded
Headers show

Commit Message

Hein-Pieter van Braam June 17, 2017, 1:08 p.m. UTC
Hi all,

This patch implements support for 3 previously unknown Interplay MVE
opcodes. These opcodes together implement support for 2 additional
video frame formats.

This is my first time trying to contribute to ffmpeg so I expect this
code to not be entirely up to snuff, I'm interested in getting this
merged so any comments are welcome and I'll make any necessary changes.
I have ran patchcheck over it and fix most of the issues it found.

I'm working with Multimedia Mike to get these opcodes documented on the
multimedia.cx wiki.

You may notice something strange going on with decoding opcode 10
movies, the reason for that is that the codec appears to need access to
a block since before the last time it was changed, this is not
necessarily the last displayed frame. To implement this I decode to two
extra AVFrame's, swapping them after each decode, and copying only
changed blocks from the current decoding frame to the final display
frame. I think that my implementation is probably more convoluted than
it needs to be, any suggestions on that front would be most welcome
also.

There's a bug in FFmpeg master currently that prevents the MVE decoder
from signaling that the end of the file has been reached. I've made no
attempt to fix that in this patch. I'll create a separate patch to fix
this issue.

- HP
diff mbox

Patch

From 1f6d7a95748bb9f567c9cb97f2be3ff5c43140ea 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 | 279 +++++++++++++++++++++++++++++++++++++++++---
 libavformat/ipmovie.c       | 101 ++++++++++++----
 2 files changed, 341 insertions(+), 39 deletions(-)

diff --git a/libavcodec/interplayvideo.c b/libavcodec/interplayvideo.c
index df3314d..d82dc0e 100644
--- a/libavcodec/interplayvideo.c
+++ b/libavcodec/interplayvideo.c
@@ -55,8 +55,17 @@  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;
@@ -903,7 +912,189 @@  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;
+    const unsigned char* decode_map_p;
+    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);
+
+    decode_map_p = s->decoding_map;
+    for (y = 0; y < s->avctx->height; y += 8) {
+        for (x = 0; x < s->avctx->width; x += 8) {
+            opcode = AV_RL16(decode_map_p);
+
+            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 {
+                copy_from(s, s->second_last_frame, frame, 0, 0);
+            }
+            decode_map_p += 2;
+        }
+    }
+
+    decode_map_p = s->decoding_map;
+    for (y = 0; y < s->avctx->height; y += 8) {
+        for (x = 0; x < s->avctx->width; x += 8) {
+            opcode = AV_RL16(decode_map_p);
+
+            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);
+            }
+            decode_map_p += 2;
+        }
+    }
+
+    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;
+    const unsigned char* decode_map_p;
+    const unsigned char* skip_map_p;
+    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);
+
+    decode_map_p = s->decoding_map;
+    skip_map_p = s->skip_map;
+    skip = AV_RL16(skip_map_p);
+    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*cur_decode_frame->linesize[0];
+
+            while (skip <= 0)  {
+                if (skip != -0x8000 && !skip ) {
+                    opcode = AV_RL16(decode_map_p);
+                    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;
+                        }
+                    }
+                    decode_map_p += 2;
+                    break;
+                }
+                skip_map_p += 2;
+                skip = AV_RL16(skip_map_p);
+            }
+            skip *= 2;
+        }
+    }
+
+    decode_map_p = s->decoding_map;
+    skip_map_p = s->skip_map;
+    skip = AV_RL16(skip_map_p);
+    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*cur_decode_frame->linesize[0];
+
+            while (skip <= 0)  {
+                if (skip != -0x8000 && !skip ) {
+                    opcode = AV_RL16(decode_map_p);
+                    if (opcode < 0) {
+                        off_x = ((unsigned short)opcode - 0xC000) % cur_decode_frame->linesize[0];
+                        off_y = ((unsigned short)opcode - 0xC000) / 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) % cur_decode_frame->linesize[0];
+                        off_y = ((unsigned short)opcode - 0x4000) / cur_decode_frame->linesize[0];
+                        copy_from(s, s->cur_decode_frame, s->cur_decode_frame, off_x, off_y);
+                    }
+                    decode_map_p += 2;
+                    break;
+                }
+                skip_map_p += 2;
+                skip = AV_RL16(skip_map_p);
+            }
+            skip *= 2;
+        }
+    }
+
+    skip_map_p = s->skip_map;
+    skip = AV_RL16(skip_map_p);
+    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_map_p += 2;
+                skip = AV_RL16(skip_map_p);
+            }
+            if (changed_block) {
+                copy_from(s, s->cur_decode_frame, frame, 0, 0);
+            } else {
+                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 +1163,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 +1204,53 @@  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);
+
+            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;
+
+            /* 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;
+            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 +1266,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 +1293,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