| /* |
| * Interplay MVE Video Decoder |
| * Copyright (C) 2003 the ffmpeg project |
| * |
| * 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 |
| */ |
| |
| /** |
| * @file libavcodec/interplayvideo.c |
| * Interplay MVE Video Decoder by Mike Melanson (melanson@pcisys.net) |
| * For more information about the Interplay MVE format, visit: |
| * http://www.pcisys.net/~melanson/codecs/interplay-mve.txt |
| * This code is written in such a way that the identifiers match up |
| * with the encoding descriptions in the document. |
| * |
| * This decoder presently only supports a PAL8 output colorspace. |
| * |
| * An Interplay video frame consists of 2 parts: The decoding map and |
| * the video data. A demuxer must load these 2 parts together in a single |
| * buffer before sending it through the stream to this decoder. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "avcodec.h" |
| #include "bytestream.h" |
| #include "dsputil.h" |
| |
| #define PALETTE_COUNT 256 |
| |
| /* debugging support */ |
| #define DEBUG_INTERPLAY 0 |
| #if DEBUG_INTERPLAY |
| #define debug_interplay(x,...) av_log(NULL, AV_LOG_DEBUG, x, __VA_ARGS__) |
| #else |
| static inline void debug_interplay(const char *format, ...) { } |
| #endif |
| |
| typedef struct IpvideoContext { |
| |
| AVCodecContext *avctx; |
| DSPContext dsp; |
| AVFrame second_last_frame; |
| AVFrame last_frame; |
| AVFrame current_frame; |
| const unsigned char *decoding_map; |
| int decoding_map_size; |
| |
| const unsigned char *buf; |
| int size; |
| |
| const unsigned char *stream_ptr; |
| const unsigned char *stream_end; |
| unsigned char *pixel_ptr; |
| int line_inc; |
| int stride; |
| int upper_motion_limit_offset; |
| |
| } IpvideoContext; |
| |
| #define CHECK_STREAM_PTR(n) \ |
| if ((s->stream_ptr + n) > s->stream_end) { \ |
| av_log(s->avctx, AV_LOG_ERROR, "Interplay video warning: stream_ptr out of bounds (%p >= %p)\n", \ |
| s->stream_ptr + n, s->stream_end); \ |
| return -1; \ |
| } |
| |
| #define COPY_FROM_CURRENT() \ |
| motion_offset = current_offset; \ |
| motion_offset += y * s->stride; \ |
| motion_offset += x; \ |
| if (motion_offset < 0) { \ |
| av_log(s->avctx, AV_LOG_ERROR, " Interplay video: motion offset < 0 (%d)\n", motion_offset); \ |
| return -1; \ |
| } else if (motion_offset > s->upper_motion_limit_offset) { \ |
| av_log(s->avctx, AV_LOG_ERROR, " Interplay video: motion offset above limit (%d >= %d)\n", \ |
| motion_offset, s->upper_motion_limit_offset); \ |
| return -1; \ |
| } \ |
| s->dsp.put_pixels_tab[0][0](s->pixel_ptr, \ |
| s->current_frame.data[0] + motion_offset, s->stride, 8); |
| |
| #define COPY_FROM_PREVIOUS() \ |
| motion_offset = current_offset; \ |
| motion_offset += y * s->stride; \ |
| motion_offset += x; \ |
| if (motion_offset < 0) { \ |
| av_log(s->avctx, AV_LOG_ERROR, " Interplay video: motion offset < 0 (%d)\n", motion_offset); \ |
| return -1; \ |
| } else if (motion_offset > s->upper_motion_limit_offset) { \ |
| av_log(s->avctx, AV_LOG_ERROR, " Interplay video: motion offset above limit (%d >= %d)\n", \ |
| motion_offset, s->upper_motion_limit_offset); \ |
| return -1; \ |
| } \ |
| s->dsp.put_pixels_tab[0][0](s->pixel_ptr, \ |
| s->last_frame.data[0] + motion_offset, s->stride, 8); |
| |
| #define COPY_FROM_SECOND_LAST() \ |
| motion_offset = current_offset; \ |
| motion_offset += y * s->stride; \ |
| motion_offset += x; \ |
| if (motion_offset < 0) { \ |
| av_log(s->avctx, AV_LOG_ERROR, " Interplay video: motion offset < 0 (%d)\n", motion_offset); \ |
| return -1; \ |
| } else if (motion_offset > s->upper_motion_limit_offset) { \ |
| av_log(s->avctx, AV_LOG_ERROR, " Interplay video: motion offset above limit (%d >= %d)\n", \ |
| motion_offset, s->upper_motion_limit_offset); \ |
| return -1; \ |
| } \ |
| s->dsp.put_pixels_tab[0][0](s->pixel_ptr, \ |
| s->second_last_frame.data[0] + motion_offset, s->stride, 8); |
| |
| static int ipvideo_decode_block_opcode_0x0(IpvideoContext *s) |
| { |
| int x, y; |
| int motion_offset; |
| int current_offset = s->pixel_ptr - s->current_frame.data[0]; |
| |
| /* copy a block from the previous frame */ |
| x = y = 0; |
| COPY_FROM_PREVIOUS(); |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0x1(IpvideoContext *s) |
| { |
| int x, y; |
| int motion_offset; |
| int current_offset = s->pixel_ptr - s->current_frame.data[0]; |
| |
| /* copy block from 2 frames ago */ |
| x = y = 0; |
| COPY_FROM_SECOND_LAST(); |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0x2(IpvideoContext *s) |
| { |
| unsigned char B; |
| int x, y; |
| int motion_offset; |
| int current_offset = s->pixel_ptr - s->current_frame.data[0]; |
| |
| /* copy block from 2 frames ago using a motion vector; need 1 more byte */ |
| CHECK_STREAM_PTR(1); |
| B = *s->stream_ptr++; |
| |
| if (B < 56) { |
| x = 8 + (B % 7); |
| y = B / 7; |
| } else { |
| x = -14 + ((B - 56) % 29); |
| y = 8 + ((B - 56) / 29); |
| } |
| |
| debug_interplay (" motion byte = %d, (x, y) = (%d, %d)\n", B, x, y); |
| COPY_FROM_SECOND_LAST(); |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0x3(IpvideoContext *s) |
| { |
| unsigned char B; |
| int x, y; |
| int motion_offset; |
| int current_offset = s->pixel_ptr - s->current_frame.data[0]; |
| |
| /* copy 8x8 block from current frame from an up/left block */ |
| |
| /* need 1 more byte for motion */ |
| CHECK_STREAM_PTR(1); |
| B = *s->stream_ptr++; |
| |
| if (B < 56) { |
| x = -(8 + (B % 7)); |
| y = -(B / 7); |
| } else { |
| x = -(-14 + ((B - 56) % 29)); |
| y = -( 8 + ((B - 56) / 29)); |
| } |
| |
| debug_interplay (" motion byte = %d, (x, y) = (%d, %d)\n", B, x, y); |
| COPY_FROM_CURRENT(); |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0x4(IpvideoContext *s) |
| { |
| int x, y; |
| unsigned char B, BL, BH; |
| int motion_offset; |
| int current_offset = s->pixel_ptr - s->current_frame.data[0]; |
| |
| /* copy a block from the previous frame; need 1 more byte */ |
| CHECK_STREAM_PTR(1); |
| |
| B = *s->stream_ptr++; |
| BL = B & 0x0F; |
| BH = (B >> 4) & 0x0F; |
| x = -8 + BL; |
| y = -8 + BH; |
| |
| debug_interplay (" motion byte = %d, (x, y) = (%d, %d)\n", B, x, y); |
| COPY_FROM_PREVIOUS(); |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0x5(IpvideoContext *s) |
| { |
| signed char x, y; |
| int motion_offset; |
| int current_offset = s->pixel_ptr - s->current_frame.data[0]; |
| |
| /* copy a block from the previous frame using an expanded range; |
| * need 2 more bytes */ |
| CHECK_STREAM_PTR(2); |
| |
| x = *s->stream_ptr++; |
| y = *s->stream_ptr++; |
| |
| debug_interplay (" motion bytes = %d, %d\n", x, y); |
| COPY_FROM_PREVIOUS(); |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0x6(IpvideoContext *s) |
| { |
| /* mystery opcode? skip multiple blocks? */ |
| av_log(s->avctx, AV_LOG_ERROR, " Interplay video: Help! Mystery opcode 0x6 seen\n"); |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0x7(IpvideoContext *s) |
| { |
| int x, y; |
| unsigned char P0, P1; |
| unsigned char B[8]; |
| unsigned int flags; |
| int bitmask; |
| |
| /* 2-color encoding */ |
| CHECK_STREAM_PTR(2); |
| |
| P0 = *s->stream_ptr++; |
| P1 = *s->stream_ptr++; |
| |
| if (P0 <= P1) { |
| |
| /* need 8 more bytes from the stream */ |
| CHECK_STREAM_PTR(8); |
| for (y = 0; y < 8; y++) |
| B[y] = *s->stream_ptr++; |
| |
| for (y = 0; y < 8; y++) { |
| flags = B[y]; |
| for (x = 0x01; x <= 0x80; x <<= 1) { |
| if (flags & x) |
| *s->pixel_ptr++ = P1; |
| else |
| *s->pixel_ptr++ = P0; |
| } |
| s->pixel_ptr += s->line_inc; |
| } |
| |
| } else { |
| |
| /* need 2 more bytes from the stream */ |
| CHECK_STREAM_PTR(2); |
| |
| flags = bytestream_get_le16(&s->stream_ptr); |
| bitmask = 0x0001; |
| for (y = 0; y < 8; y += 2) { |
| for (x = 0; x < 8; x += 2, bitmask <<= 1) { |
| if (flags & bitmask) { |
| *(s->pixel_ptr + x) = P1; |
| *(s->pixel_ptr + x + 1) = P1; |
| *(s->pixel_ptr + s->stride + x) = P1; |
| *(s->pixel_ptr + s->stride + x + 1) = P1; |
| } else { |
| *(s->pixel_ptr + x) = P0; |
| *(s->pixel_ptr + x + 1) = P0; |
| *(s->pixel_ptr + s->stride + x) = P0; |
| *(s->pixel_ptr + s->stride + x + 1) = P0; |
| } |
| } |
| s->pixel_ptr += s->stride * 2; |
| } |
| } |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0x8(IpvideoContext *s) |
| { |
| int x, y; |
| unsigned char P[8]; |
| unsigned char B[8]; |
| unsigned int flags = 0; |
| unsigned int bitmask = 0; |
| unsigned char P0 = 0, P1 = 0; |
| int lower_half = 0; |
| |
| /* 2-color encoding for each 4x4 quadrant, or 2-color encoding on |
| * either top and bottom or left and right halves */ |
| CHECK_STREAM_PTR(2); |
| |
| P[0] = *s->stream_ptr++; |
| P[1] = *s->stream_ptr++; |
| |
| if (P[0] <= P[1]) { |
| |
| /* need 12 more bytes */ |
| CHECK_STREAM_PTR(12); |
| B[0] = *s->stream_ptr++; B[1] = *s->stream_ptr++; |
| P[2] = *s->stream_ptr++; P[3] = *s->stream_ptr++; |
| B[2] = *s->stream_ptr++; B[3] = *s->stream_ptr++; |
| P[4] = *s->stream_ptr++; P[5] = *s->stream_ptr++; |
| B[4] = *s->stream_ptr++; B[5] = *s->stream_ptr++; |
| P[6] = *s->stream_ptr++; P[7] = *s->stream_ptr++; |
| B[6] = *s->stream_ptr++; B[7] = *s->stream_ptr++; |
| |
| for (y = 0; y < 8; y++) { |
| |
| /* time to reload flags? */ |
| if (y == 0) { |
| flags = |
| ((B[0] & 0xF0) << 4) | ((B[4] & 0xF0) << 8) | |
| ((B[0] & 0x0F) ) | ((B[4] & 0x0F) << 4) | |
| ((B[1] & 0xF0) << 20) | ((B[5] & 0xF0) << 24) | |
| ((B[1] & 0x0F) << 16) | ((B[5] & 0x0F) << 20); |
| bitmask = 0x00000001; |
| lower_half = 0; /* still on top half */ |
| } else if (y == 4) { |
| flags = |
| ((B[2] & 0xF0) << 4) | ((B[6] & 0xF0) << 8) | |
| ((B[2] & 0x0F) ) | ((B[6] & 0x0F) << 4) | |
| ((B[3] & 0xF0) << 20) | ((B[7] & 0xF0) << 24) | |
| ((B[3] & 0x0F) << 16) | ((B[7] & 0x0F) << 20); |
| bitmask = 0x00000001; |
| lower_half = 2; |
| } |
| |
| for (x = 0; x < 8; x++, bitmask <<= 1) { |
| /* get the pixel values ready for this quadrant */ |
| if (x == 0) { |
| P0 = P[lower_half + 0]; |
| P1 = P[lower_half + 1]; |
| } else if (x == 4) { |
| P0 = P[lower_half + 4]; |
| P1 = P[lower_half + 5]; |
| } |
| |
| if (flags & bitmask) |
| *s->pixel_ptr++ = P1; |
| else |
| *s->pixel_ptr++ = P0; |
| } |
| s->pixel_ptr += s->line_inc; |
| } |
| |
| } else { |
| |
| /* need 10 more bytes */ |
| CHECK_STREAM_PTR(10); |
| B[0] = *s->stream_ptr++; B[1] = *s->stream_ptr++; |
| B[2] = *s->stream_ptr++; B[3] = *s->stream_ptr++; |
| P[2] = *s->stream_ptr++; P[3] = *s->stream_ptr++; |
| B[4] = *s->stream_ptr++; B[5] = *s->stream_ptr++; |
| B[6] = *s->stream_ptr++; B[7] = *s->stream_ptr++; |
| |
| if (P[2] <= P[3]) { |
| |
| /* vertical split; left & right halves are 2-color encoded */ |
| |
| for (y = 0; y < 8; y++) { |
| |
| /* time to reload flags? */ |
| if (y == 0) { |
| flags = |
| ((B[0] & 0xF0) << 4) | ((B[4] & 0xF0) << 8) | |
| ((B[0] & 0x0F) ) | ((B[4] & 0x0F) << 4) | |
| ((B[1] & 0xF0) << 20) | ((B[5] & 0xF0) << 24) | |
| ((B[1] & 0x0F) << 16) | ((B[5] & 0x0F) << 20); |
| bitmask = 0x00000001; |
| } else if (y == 4) { |
| flags = |
| ((B[2] & 0xF0) << 4) | ((B[6] & 0xF0) << 8) | |
| ((B[2] & 0x0F) ) | ((B[6] & 0x0F) << 4) | |
| ((B[3] & 0xF0) << 20) | ((B[7] & 0xF0) << 24) | |
| ((B[3] & 0x0F) << 16) | ((B[7] & 0x0F) << 20); |
| bitmask = 0x00000001; |
| } |
| |
| for (x = 0; x < 8; x++, bitmask <<= 1) { |
| /* get the pixel values ready for this half */ |
| if (x == 0) { |
| P0 = P[0]; |
| P1 = P[1]; |
| } else if (x == 4) { |
| P0 = P[2]; |
| P1 = P[3]; |
| } |
| |
| if (flags & bitmask) |
| *s->pixel_ptr++ = P1; |
| else |
| *s->pixel_ptr++ = P0; |
| } |
| s->pixel_ptr += s->line_inc; |
| } |
| |
| } else { |
| |
| /* horizontal split; top & bottom halves are 2-color encoded */ |
| |
| for (y = 0; y < 8; y++) { |
| |
| flags = B[y]; |
| if (y == 0) { |
| P0 = P[0]; |
| P1 = P[1]; |
| } else if (y == 4) { |
| P0 = P[2]; |
| P1 = P[3]; |
| } |
| |
| for (bitmask = 0x01; bitmask <= 0x80; bitmask <<= 1) { |
| |
| if (flags & bitmask) |
| *s->pixel_ptr++ = P1; |
| else |
| *s->pixel_ptr++ = P0; |
| } |
| s->pixel_ptr += s->line_inc; |
| } |
| } |
| } |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0x9(IpvideoContext *s) |
| { |
| int x, y; |
| unsigned char P[4]; |
| unsigned int flags = 0; |
| int shifter = 0; |
| unsigned char pix; |
| |
| /* 4-color encoding */ |
| CHECK_STREAM_PTR(4); |
| |
| for (y = 0; y < 4; y++) |
| P[y] = *s->stream_ptr++; |
| |
| if ((P[0] <= P[1]) && (P[2] <= P[3])) { |
| |
| /* 1 of 4 colors for each pixel, need 16 more bytes */ |
| CHECK_STREAM_PTR(16); |
| |
| for (y = 0; y < 8; y++) { |
| /* get the next set of 8 2-bit flags */ |
| flags = bytestream_get_le16(&s->stream_ptr); |
| for (x = 0, shifter = 0; x < 8; x++, shifter += 2) { |
| *s->pixel_ptr++ = P[(flags >> shifter) & 0x03]; |
| } |
| s->pixel_ptr += s->line_inc; |
| } |
| |
| } else if ((P[0] <= P[1]) && (P[2] > P[3])) { |
| |
| /* 1 of 4 colors for each 2x2 block, need 4 more bytes */ |
| CHECK_STREAM_PTR(4); |
| |
| flags = bytestream_get_le32(&s->stream_ptr); |
| shifter = 0; |
| |
| for (y = 0; y < 8; y += 2) { |
| for (x = 0; x < 8; x += 2, shifter += 2) { |
| pix = P[(flags >> shifter) & 0x03]; |
| *(s->pixel_ptr + x) = pix; |
| *(s->pixel_ptr + x + 1) = pix; |
| *(s->pixel_ptr + s->stride + x) = pix; |
| *(s->pixel_ptr + s->stride + x + 1) = pix; |
| } |
| s->pixel_ptr += s->stride * 2; |
| } |
| |
| } else if ((P[0] > P[1]) && (P[2] <= P[3])) { |
| |
| /* 1 of 4 colors for each 2x1 block, need 8 more bytes */ |
| CHECK_STREAM_PTR(8); |
| |
| for (y = 0; y < 8; y++) { |
| /* time to reload flags? */ |
| if ((y == 0) || (y == 4)) { |
| flags = bytestream_get_le32(&s->stream_ptr); |
| shifter = 0; |
| } |
| for (x = 0; x < 8; x += 2, shifter += 2) { |
| pix = P[(flags >> shifter) & 0x03]; |
| *(s->pixel_ptr + x) = pix; |
| *(s->pixel_ptr + x + 1) = pix; |
| } |
| s->pixel_ptr += s->stride; |
| } |
| |
| } else { |
| |
| /* 1 of 4 colors for each 1x2 block, need 8 more bytes */ |
| CHECK_STREAM_PTR(8); |
| |
| for (y = 0; y < 8; y += 2) { |
| /* time to reload flags? */ |
| if ((y == 0) || (y == 4)) { |
| flags = bytestream_get_le32(&s->stream_ptr); |
| shifter = 0; |
| } |
| for (x = 0; x < 8; x++, shifter += 2) { |
| pix = P[(flags >> shifter) & 0x03]; |
| *(s->pixel_ptr + x) = pix; |
| *(s->pixel_ptr + s->stride + x) = pix; |
| } |
| s->pixel_ptr += s->stride * 2; |
| } |
| } |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0xA(IpvideoContext *s) |
| { |
| int x, y; |
| unsigned char P[16]; |
| unsigned char B[16]; |
| int flags = 0; |
| int shifter = 0; |
| int index; |
| int split; |
| int lower_half; |
| |
| /* 4-color encoding for each 4x4 quadrant, or 4-color encoding on |
| * either top and bottom or left and right halves */ |
| CHECK_STREAM_PTR(4); |
| |
| for (y = 0; y < 4; y++) |
| P[y] = *s->stream_ptr++; |
| |
| if (P[0] <= P[1]) { |
| |
| /* 4-color encoding for each quadrant; need 28 more bytes */ |
| CHECK_STREAM_PTR(28); |
| |
| for (y = 0; y < 4; y++) |
| B[y] = *s->stream_ptr++; |
| for (y = 4; y < 16; y += 4) { |
| for (x = y; x < y + 4; x++) |
| P[x] = *s->stream_ptr++; |
| for (x = y; x < y + 4; x++) |
| B[x] = *s->stream_ptr++; |
| } |
| |
| for (y = 0; y < 8; y++) { |
| |
| lower_half = (y >= 4) ? 4 : 0; |
| flags = (B[y + 8] << 8) | B[y]; |
| |
| for (x = 0, shifter = 0; x < 8; x++, shifter += 2) { |
| split = (x >= 4) ? 8 : 0; |
| index = split + lower_half + ((flags >> shifter) & 0x03); |
| *s->pixel_ptr++ = P[index]; |
| } |
| |
| s->pixel_ptr += s->line_inc; |
| } |
| |
| } else { |
| |
| /* 4-color encoding for either left and right or top and bottom |
| * halves; need 20 more bytes */ |
| CHECK_STREAM_PTR(20); |
| |
| for (y = 0; y < 8; y++) |
| B[y] = *s->stream_ptr++; |
| for (y = 4; y < 8; y++) |
| P[y] = *s->stream_ptr++; |
| for (y = 8; y < 16; y++) |
| B[y] = *s->stream_ptr++; |
| |
| if (P[4] <= P[5]) { |
| |
| /* block is divided into left and right halves */ |
| for (y = 0; y < 8; y++) { |
| |
| flags = (B[y + 8] << 8) | B[y]; |
| split = 0; |
| |
| for (x = 0, shifter = 0; x < 8; x++, shifter += 2) { |
| if (x == 4) |
| split = 4; |
| *s->pixel_ptr++ = P[split + ((flags >> shifter) & 0x03)]; |
| } |
| |
| s->pixel_ptr += s->line_inc; |
| } |
| |
| } else { |
| |
| /* block is divided into top and bottom halves */ |
| split = 0; |
| for (y = 0; y < 8; y++) { |
| |
| flags = (B[y * 2 + 1] << 8) | B[y * 2]; |
| if (y == 4) |
| split = 4; |
| |
| for (x = 0, shifter = 0; x < 8; x++, shifter += 2) |
| *s->pixel_ptr++ = P[split + ((flags >> shifter) & 0x03)]; |
| |
| s->pixel_ptr += s->line_inc; |
| } |
| } |
| } |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0xB(IpvideoContext *s) |
| { |
| int x, y; |
| |
| /* 64-color encoding (each pixel in block is a different color) */ |
| CHECK_STREAM_PTR(64); |
| |
| for (y = 0; y < 8; y++) { |
| for (x = 0; x < 8; x++) { |
| *s->pixel_ptr++ = *s->stream_ptr++; |
| } |
| s->pixel_ptr += s->line_inc; |
| } |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0xC(IpvideoContext *s) |
| { |
| int x, y; |
| unsigned char pix; |
| |
| /* 16-color block encoding: each 2x2 block is a different color */ |
| CHECK_STREAM_PTR(16); |
| |
| for (y = 0; y < 8; y += 2) { |
| for (x = 0; x < 8; x += 2) { |
| pix = *s->stream_ptr++; |
| *(s->pixel_ptr + x) = pix; |
| *(s->pixel_ptr + x + 1) = pix; |
| *(s->pixel_ptr + s->stride + x) = pix; |
| *(s->pixel_ptr + s->stride + x + 1) = pix; |
| } |
| s->pixel_ptr += s->stride * 2; |
| } |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0xD(IpvideoContext *s) |
| { |
| int x, y; |
| unsigned char P[4]; |
| unsigned char index = 0; |
| |
| /* 4-color block encoding: each 4x4 block is a different color */ |
| CHECK_STREAM_PTR(4); |
| |
| for (y = 0; y < 4; y++) |
| P[y] = *s->stream_ptr++; |
| |
| for (y = 0; y < 8; y++) { |
| if (y < 4) |
| index = 0; |
| else |
| index = 2; |
| |
| for (x = 0; x < 8; x++) { |
| if (x == 4) |
| index++; |
| *s->pixel_ptr++ = P[index]; |
| } |
| s->pixel_ptr += s->line_inc; |
| } |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0xE(IpvideoContext *s) |
| { |
| int x, y; |
| unsigned char pix; |
| |
| /* 1-color encoding: the whole block is 1 solid color */ |
| CHECK_STREAM_PTR(1); |
| pix = *s->stream_ptr++; |
| |
| for (y = 0; y < 8; y++) { |
| for (x = 0; x < 8; x++) { |
| *s->pixel_ptr++ = pix; |
| } |
| s->pixel_ptr += s->line_inc; |
| } |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int ipvideo_decode_block_opcode_0xF(IpvideoContext *s) |
| { |
| int x, y; |
| unsigned char sample0, sample1; |
| |
| /* dithered encoding */ |
| CHECK_STREAM_PTR(2); |
| sample0 = *s->stream_ptr++; |
| sample1 = *s->stream_ptr++; |
| |
| for (y = 0; y < 8; y++) { |
| for (x = 0; x < 8; x += 2) { |
| if (y & 1) { |
| *s->pixel_ptr++ = sample1; |
| *s->pixel_ptr++ = sample0; |
| } else { |
| *s->pixel_ptr++ = sample0; |
| *s->pixel_ptr++ = sample1; |
| } |
| } |
| s->pixel_ptr += s->line_inc; |
| } |
| |
| /* report success */ |
| return 0; |
| } |
| |
| static int (*ipvideo_decode_block[16])(IpvideoContext *s); |
| |
| static void ipvideo_decode_opcodes(IpvideoContext *s) |
| { |
| int x, y; |
| int index = 0; |
| unsigned char opcode; |
| int ret; |
| int code_counts[16]; |
| static int frame = 0; |
| |
| debug_interplay("------------------ frame %d\n", frame); |
| frame++; |
| |
| for (x = 0; x < 16; x++) |
| code_counts[x] = 0; |
| |
| /* this is PAL8, so make the palette available */ |
| memcpy(s->current_frame.data[1], s->avctx->palctrl->palette, PALETTE_COUNT * 4); |
| |
| s->stride = s->current_frame.linesize[0]; |
| s->stream_ptr = s->buf + 14; /* data starts 14 bytes in */ |
| s->stream_end = s->buf + s->size; |
| s->line_inc = s->stride - 8; |
| s->upper_motion_limit_offset = (s->avctx->height - 8) * s->stride |
| + s->avctx->width - 8; |
| |
| for (y = 0; y < (s->stride * s->avctx->height); y += s->stride * 8) { |
| for (x = y; x < y + s->avctx->width; x += 8) { |
| /* bottom nibble first, then top nibble (which makes it |
| * hard to use a GetBitcontext) */ |
| if (index & 1) |
| opcode = s->decoding_map[index >> 1] >> 4; |
| else |
| opcode = s->decoding_map[index >> 1] & 0xF; |
| index++; |
| |
| debug_interplay(" block @ (%3d, %3d): encoding 0x%X, data ptr @ %p\n", |
| x - y, y / s->stride, opcode, s->stream_ptr); |
| code_counts[opcode]++; |
| |
| s->pixel_ptr = s->current_frame.data[0] + x; |
| ret = ipvideo_decode_block[opcode](s); |
| if (ret != 0) { |
| av_log(s->avctx, AV_LOG_ERROR, " Interplay video: decode problem on frame %d, @ block (%d, %d)\n", |
| frame, x - y, y / s->stride); |
| return; |
| } |
| } |
| } |
| if ((s->stream_ptr != s->stream_end) && |
| (s->stream_ptr + 1 != s->stream_end)) { |
| av_log(s->avctx, AV_LOG_ERROR, " Interplay video: decode finished with %td bytes left over\n", |
| s->stream_end - s->stream_ptr); |
| } |
| } |
| |
| static av_cold int ipvideo_decode_init(AVCodecContext *avctx) |
| { |
| IpvideoContext *s = avctx->priv_data; |
| |
| s->avctx = avctx; |
| |
| if (s->avctx->palctrl == NULL) { |
| av_log(avctx, AV_LOG_ERROR, " Interplay video: palette expected.\n"); |
| return -1; |
| } |
| |
| avctx->pix_fmt = PIX_FMT_PAL8; |
| dsputil_init(&s->dsp, avctx); |
| |
| /* decoding map contains 4 bits of information per 8x8 block */ |
| s->decoding_map_size = avctx->width * avctx->height / (8 * 8 * 2); |
| |
| /* assign block decode functions */ |
| ipvideo_decode_block[0x0] = ipvideo_decode_block_opcode_0x0; |
| ipvideo_decode_block[0x1] = ipvideo_decode_block_opcode_0x1; |
| ipvideo_decode_block[0x2] = ipvideo_decode_block_opcode_0x2; |
| ipvideo_decode_block[0x3] = ipvideo_decode_block_opcode_0x3; |
| ipvideo_decode_block[0x4] = ipvideo_decode_block_opcode_0x4; |
| ipvideo_decode_block[0x5] = ipvideo_decode_block_opcode_0x5; |
| ipvideo_decode_block[0x6] = ipvideo_decode_block_opcode_0x6; |
| ipvideo_decode_block[0x7] = ipvideo_decode_block_opcode_0x7; |
| ipvideo_decode_block[0x8] = ipvideo_decode_block_opcode_0x8; |
| ipvideo_decode_block[0x9] = ipvideo_decode_block_opcode_0x9; |
| ipvideo_decode_block[0xA] = ipvideo_decode_block_opcode_0xA; |
| ipvideo_decode_block[0xB] = ipvideo_decode_block_opcode_0xB; |
| ipvideo_decode_block[0xC] = ipvideo_decode_block_opcode_0xC; |
| ipvideo_decode_block[0xD] = ipvideo_decode_block_opcode_0xD; |
| ipvideo_decode_block[0xE] = ipvideo_decode_block_opcode_0xE; |
| ipvideo_decode_block[0xF] = ipvideo_decode_block_opcode_0xF; |
| |
| s->current_frame.data[0] = s->last_frame.data[0] = |
| s->second_last_frame.data[0] = NULL; |
| |
| return 0; |
| } |
| |
| static int ipvideo_decode_frame(AVCodecContext *avctx, |
| void *data, int *data_size, |
| const uint8_t *buf, int buf_size) |
| { |
| IpvideoContext *s = avctx->priv_data; |
| AVPaletteControl *palette_control = avctx->palctrl; |
| |
| /* compressed buffer needs to be large enough to at least hold an entire |
| * decoding map */ |
| if (buf_size < s->decoding_map_size) |
| return buf_size; |
| |
| s->decoding_map = buf; |
| s->buf = buf + s->decoding_map_size; |
| s->size = buf_size - s->decoding_map_size; |
| |
| s->current_frame.reference = 3; |
| if (avctx->get_buffer(avctx, &s->current_frame)) { |
| av_log(avctx, AV_LOG_ERROR, " Interplay Video: get_buffer() failed\n"); |
| return -1; |
| } |
| |
| ipvideo_decode_opcodes(s); |
| |
| if (palette_control->palette_changed) { |
| palette_control->palette_changed = 0; |
| s->current_frame.palette_has_changed = 1; |
| } |
| |
| *data_size = sizeof(AVFrame); |
| *(AVFrame*)data = s->current_frame; |
| |
| /* shuffle frames */ |
| if (s->second_last_frame.data[0]) |
| avctx->release_buffer(avctx, &s->second_last_frame); |
| s->second_last_frame = s->last_frame; |
| s->last_frame = s->current_frame; |
| s->current_frame.data[0] = NULL; /* catch any access attempts */ |
| |
| /* report that the buffer was completely consumed */ |
| return buf_size; |
| } |
| |
| static av_cold int ipvideo_decode_end(AVCodecContext *avctx) |
| { |
| IpvideoContext *s = avctx->priv_data; |
| |
| /* release the last frame */ |
| if (s->last_frame.data[0]) |
| avctx->release_buffer(avctx, &s->last_frame); |
| if (s->second_last_frame.data[0]) |
| avctx->release_buffer(avctx, &s->second_last_frame); |
| |
| return 0; |
| } |
| |
| AVCodec interplay_video_decoder = { |
| "interplayvideo", |
| CODEC_TYPE_VIDEO, |
| CODEC_ID_INTERPLAY_VIDEO, |
| sizeof(IpvideoContext), |
| ipvideo_decode_init, |
| NULL, |
| ipvideo_decode_end, |
| ipvideo_decode_frame, |
| CODEC_CAP_DR1, |
| .long_name = NULL_IF_CONFIG_SMALL("Interplay MVE video"), |
| }; |