Hi all,
I seemed to have done something wrong when fixing the patchcheck
problems originally. Apparently I didn't build between making my
'fixes' and creating the patch. The previous patch doesn't compile.
I fixed this in this version, my apologies for the noise.
I've also fixed reading garbage data from the 'last frame' on the very
first frame.
- HP
On Sat, 2017-06-17 at 15:08 +0200, Hein-Pieter van Braam wrote:
> 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
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
From 1e1cde69e5728f937d79815bea8b9661fb69a8dd Mon Sep 17 00:00:00 2001
From: Hein-Pieter van Braam <h...@tmm.cx>
Date: Fri, 16 Jun 2017 22:02:43 +0200
Subject: [PATCH] Implement support for interplay MVE 0x06 and 0x10
---
libavcodec/interplayvideo.c | 281 +++++++++++++++++++++++++++++++++++++++++---
libavformat/ipmovie.c | 101 ++++++++++++----
2 files changed, 343 insertions(+), 39 deletions(-)
diff --git a/libavcodec/interplayvideo.c b/libavcodec/interplayvideo.c
index df3314d..9542e5f 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,191 @@ 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*s->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*s->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) % 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);
+ }
+ 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 {
+ /* 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 +1165,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 +1206,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 +1268,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 +1295,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
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
http://ffmpeg.org/mailman/listinfo/ffmpeg-devel