| /* | 
 |  * FM Screen Capture Codec decoder | 
 |  * | 
 |  * Copyright (c) 2017 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 <string.h> | 
 |  | 
 | #include "libavutil/mem.h" | 
 | #include "avcodec.h" | 
 | #include "bytestream.h" | 
 | #include "codec_internal.h" | 
 | #include "decode.h" | 
 |  | 
 | #define BLOCK_HEIGHT 112u | 
 | #define BLOCK_WIDTH  84u | 
 |  | 
 | typedef struct InterBlock { | 
 |     int      w, h; | 
 |     int      size; | 
 |     int      xor; | 
 | } InterBlock; | 
 |  | 
 | typedef struct FMVCContext { | 
 |     GetByteContext  gb; | 
 |     PutByteContext  pb; | 
 |     uint8_t        *buffer; | 
 |     size_t          buffer_size; | 
 |     uint8_t        *pbuffer; | 
 |     size_t          pbuffer_size; | 
 |     ptrdiff_t       stride; | 
 |     int             bpp; | 
 |     int             yb, xb; | 
 |     InterBlock     *blocks; | 
 |     unsigned        nb_blocks; | 
 | } FMVCContext; | 
 |  | 
 | static int decode_type2(GetByteContext *gb, PutByteContext *pb) | 
 | { | 
 |     unsigned repeat = 0, first = 1, opcode = 0; | 
 |     int i, len, pos; | 
 |  | 
 |     while (bytestream2_get_bytes_left(gb) > 0) { | 
 |         GetByteContext gbc; | 
 |  | 
 |         while (bytestream2_get_bytes_left(gb) > 0) { | 
 |             if (first) { | 
 |                 first = 0; | 
 |                 if (bytestream2_peek_byte(gb) > 17) { | 
 |                     len = bytestream2_get_byte(gb) - 17; | 
 |                     if (len < 4) { | 
 |                         do { | 
 |                             bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                             --len; | 
 |                         } while (len); | 
 |                         opcode = bytestream2_peek_byte(gb); | 
 |                         continue; | 
 |                     } else { | 
 |                         do { | 
 |                             bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                             --len; | 
 |                         } while (len); | 
 |                         opcode = bytestream2_peek_byte(gb); | 
 |                         if (opcode < 0x10) { | 
 |                             bytestream2_skip(gb, 1); | 
 |                             pos = - (opcode >> 2) - 4 * bytestream2_get_byte(gb) - 2049; | 
 |  | 
 |                             bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start); | 
 |                             bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET); | 
 |  | 
 |                             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                             len = opcode & 3; | 
 |                             if (!len) { | 
 |                                 repeat = 1; | 
 |                             } else { | 
 |                                 do { | 
 |                                     bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                                     --len; | 
 |                                 } while (len); | 
 |                                 opcode = bytestream2_peek_byte(gb); | 
 |                             } | 
 |                             continue; | 
 |                         } | 
 |                     } | 
 |                 } | 
 |                 repeat = 1; | 
 |             } | 
 |             if (repeat) { | 
 |                 repeat = 0; | 
 |                 opcode = bytestream2_peek_byte(gb); | 
 |                 if (opcode < 0x10) { | 
 |                     bytestream2_skip(gb, 1); | 
 |                     if (!opcode) { | 
 |                         if (!bytestream2_peek_byte(gb)) { | 
 |                             do { | 
 |                                 bytestream2_skip(gb, 1); | 
 |                                 opcode += 255; | 
 |                             } while (!bytestream2_peek_byte(gb) && bytestream2_get_bytes_left(gb) > 0); | 
 |                         } | 
 |                         opcode += bytestream2_get_byte(gb) + 15; | 
 |                     } | 
 |                     bytestream2_put_le32(pb, bytestream2_get_le32(gb)); | 
 |                     for (i = opcode - 1; i > 0; --i) | 
 |                         bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                     opcode = bytestream2_peek_byte(gb); | 
 |                     if (opcode < 0x10) { | 
 |                         bytestream2_skip(gb, 1); | 
 |                         pos = - (opcode >> 2) - 4 * bytestream2_get_byte(gb) - 2049; | 
 |  | 
 |                         bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start); | 
 |                         bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET); | 
 |  | 
 |                         bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                         bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                         bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                         len = opcode & 3; | 
 |                         if (!len) { | 
 |                             repeat = 1; | 
 |                         } else { | 
 |                             do { | 
 |                                 bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                                 --len; | 
 |                             } while (len); | 
 |                             opcode = bytestream2_peek_byte(gb); | 
 |                         } | 
 |                         continue; | 
 |                     } | 
 |                 } | 
 |             } | 
 |  | 
 |             if (opcode >= 0x40) { | 
 |                 bytestream2_skip(gb, 1); | 
 |                 pos = - ((opcode >> 2) & 7) - 1 - 8 * bytestream2_get_byte(gb); | 
 |                 len =    (opcode >> 5)      - 1; | 
 |  | 
 |                 bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start); | 
 |                 bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET); | 
 |  | 
 |                 bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                 bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                 do { | 
 |                     bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                     --len; | 
 |                 } while (len); | 
 |  | 
 |                 len = opcode & 3; | 
 |  | 
 |                 if (!len) { | 
 |                     repeat = 1; | 
 |                 } else { | 
 |                     do { | 
 |                         bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                         --len; | 
 |                     } while (len); | 
 |                     opcode = bytestream2_peek_byte(gb); | 
 |                 } | 
 |                 continue; | 
 |             } else if (opcode < 0x20) { | 
 |                 break; | 
 |             } | 
 |             len = opcode & 0x1F; | 
 |             bytestream2_skip(gb, 1); | 
 |             if (!len) { | 
 |                 if (!bytestream2_peek_byte(gb)) { | 
 |                     do { | 
 |                         bytestream2_skip(gb, 1); | 
 |                         len += 255; | 
 |                     } while (!bytestream2_peek_byte(gb) && bytestream2_get_bytes_left(gb) > 0); | 
 |                 } | 
 |                 len += bytestream2_get_byte(gb) + 31; | 
 |             } | 
 |             i = bytestream2_get_le16(gb); | 
 |             pos = - (i >> 2) - 1; | 
 |  | 
 |             bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start); | 
 |             bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET); | 
 |  | 
 |             if (len < 6 || bytestream2_tell_p(pb) - bytestream2_tell(&gbc) < 4) { | 
 |                 bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                 bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                 do { | 
 |                     bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                     --len; | 
 |                 } while (len); | 
 |             } else { | 
 |                 bytestream2_put_le32(pb, bytestream2_get_le32(&gbc)); | 
 |                 for (len = len - 2; len; --len) | 
 |                     bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |             } | 
 |             len = i & 3; | 
 |             if (!len) { | 
 |                 repeat = 1; | 
 |             } else { | 
 |                 do { | 
 |                     bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                     --len; | 
 |                 } while (len); | 
 |                 opcode = bytestream2_peek_byte(gb); | 
 |             } | 
 |         } | 
 |         bytestream2_skip(gb, 1); | 
 |         if (opcode < 0x10) { | 
 |             pos = -(opcode >> 2) - 1 - 4 * bytestream2_get_byte(gb); | 
 |  | 
 |             bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start); | 
 |             bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET); | 
 |  | 
 |             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |             len = opcode & 3; | 
 |             if (!len) { | 
 |                 repeat = 1; | 
 |             } else { | 
 |                 do { | 
 |                     bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                     --len; | 
 |                 } while (len); | 
 |                 opcode = bytestream2_peek_byte(gb); | 
 |             } | 
 |             continue; | 
 |         } | 
 |         len = opcode & 7; | 
 |         if (!len) { | 
 |             if (!bytestream2_peek_byte(gb)) { | 
 |                 do { | 
 |                     bytestream2_skip(gb, 1); | 
 |                     len += 255; | 
 |                 } while (!bytestream2_peek_byte(gb) && bytestream2_get_bytes_left(gb) > 0); | 
 |             } | 
 |             len += bytestream2_get_byte(gb) + 7; | 
 |         } | 
 |         i = bytestream2_get_le16(gb); | 
 |         pos = bytestream2_tell_p(pb) - 2048 * (opcode & 8); | 
 |         pos = pos - (i >> 2); | 
 |         if (pos == bytestream2_tell_p(pb)) | 
 |             break; | 
 |  | 
 |         pos = pos - 0x4000; | 
 |         bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start); | 
 |         bytestream2_seek(&gbc, pos, SEEK_SET); | 
 |  | 
 |         if (len < 6 || bytestream2_tell_p(pb) - bytestream2_tell(&gbc) < 4) { | 
 |             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |             do { | 
 |                 bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                 --len; | 
 |             } while (len); | 
 |         } else { | 
 |             bytestream2_put_le32(pb, bytestream2_get_le32(&gbc)); | 
 |             for (len = len - 2; len; --len) | 
 |                 bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |         } | 
 |  | 
 |         len = i & 3; | 
 |         if (!len) { | 
 |             repeat = 1; | 
 |         } else { | 
 |             do { | 
 |                 bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                 --len; | 
 |             } while (len); | 
 |             opcode = bytestream2_peek_byte(gb); | 
 |         } | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static int decode_type1(GetByteContext *gb, PutByteContext *pb) | 
 | { | 
 |     unsigned opcode = 0, len; | 
 |     int high = 0; | 
 |     int i, pos; | 
 |  | 
 |     while (bytestream2_get_bytes_left(gb) > 0) { | 
 |         GetByteContext gbc; | 
 |  | 
 |         while (bytestream2_get_bytes_left(gb) > 0) { | 
 |             while (bytestream2_get_bytes_left(gb) > 0) { | 
 |                 opcode = bytestream2_get_byte(gb); | 
 |                 high = opcode >= 0x20; | 
 |                 if (high) | 
 |                     break; | 
 |                 if (opcode) | 
 |                     break; | 
 |                 opcode = bytestream2_get_byte(gb); | 
 |                 if (opcode < 0xF8) { | 
 |                     opcode += 32; | 
 |                     break; | 
 |                 } | 
 |                 i = opcode - 0xF8; | 
 |                 if (i) { | 
 |                     len = 256; | 
 |                     do { | 
 |                         len *= 2; | 
 |                         --i; | 
 |                     } while (i); | 
 |                 } else { | 
 |                     len = 280; | 
 |                 } | 
 |                 do { | 
 |                     bytestream2_put_le32(pb, bytestream2_get_le32(gb)); | 
 |                     bytestream2_put_le32(pb, bytestream2_get_le32(gb)); | 
 |                     len -= 8; | 
 |                 } while (len && bytestream2_get_bytes_left(gb) > 0); | 
 |             } | 
 |  | 
 |             if (!high) { | 
 |                 do { | 
 |                     bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                     --opcode; | 
 |                 } while (opcode && bytestream2_get_bytes_left(gb) > 0); | 
 |  | 
 |                 while (bytestream2_get_bytes_left(gb) > 0) { | 
 |                     GetByteContext gbc; | 
 |  | 
 |                     opcode = bytestream2_get_byte(gb); | 
 |                     if (opcode >= 0x20) | 
 |                         break; | 
 |                     bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start); | 
 |  | 
 |                     pos = -(opcode | 32 * bytestream2_get_byte(gb)) - 1; | 
 |                     bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET); | 
 |                     bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                     bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                     bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                     bytestream2_put_byte(pb, bytestream2_get_byte(gb)); | 
 |                 } | 
 |             } | 
 |             high = 0; | 
 |             if (opcode < 0x40) | 
 |                 break; | 
 |             bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start); | 
 |             pos = (-((opcode & 0x1F) | 32 * bytestream2_get_byte(gb)) - 1); | 
 |             bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET); | 
 |             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |             len = (opcode >> 5) - 1; | 
 |             do { | 
 |                 bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |                 --len; | 
 |             } while (len && bytestream2_get_bytes_left(&gbc) > 0); | 
 |         } | 
 |         len = opcode & 0x1F; | 
 |         if (!len) { | 
 |             if (!bytestream2_peek_byte(gb)) { | 
 |                 do { | 
 |                     bytestream2_skip(gb, 1); | 
 |                     len += 255; | 
 |                 } while (!bytestream2_peek_byte(gb) && bytestream2_get_bytes_left(gb) > 0); | 
 |             } | 
 |             len += bytestream2_get_byte(gb) + 31; | 
 |         } | 
 |         pos = -bytestream2_get_byte(gb); | 
 |         bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start); | 
 |         bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos - (bytestream2_get_byte(gb) << 8), SEEK_SET); | 
 |         if (bytestream2_tell_p(pb) == bytestream2_tell(&gbc)) | 
 |             break; | 
 |         if (len < 5 || bytestream2_tell_p(pb) - bytestream2_tell(&gbc) < 4) { | 
 |             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |         } else { | 
 |             bytestream2_put_le32(pb, bytestream2_get_le32(&gbc)); | 
 |             len--; | 
 |         } | 
 |         do { | 
 |             bytestream2_put_byte(pb, bytestream2_get_byte(&gbc)); | 
 |             len--; | 
 |         } while (len && bytestream2_get_bytes_left(&gbc) > 0); | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static int decode_frame(AVCodecContext *avctx, AVFrame *frame, | 
 |                         int *got_frame, AVPacket *avpkt) | 
 | { | 
 |     FMVCContext *s = avctx->priv_data; | 
 |     GetByteContext *gb = &s->gb; | 
 |     PutByteContext *pb = &s->pb; | 
 |     int ret, y, x; | 
 |     int key_frame; | 
 |  | 
 |     if (avpkt->size < 8) | 
 |         return AVERROR_INVALIDDATA; | 
 |  | 
 |     bytestream2_init(gb, avpkt->data, avpkt->size); | 
 |     bytestream2_skip(gb, 2); | 
 |  | 
 |     key_frame = !!bytestream2_get_le16(gb); | 
 |  | 
 |     if (key_frame) { | 
 |         const uint8_t *src; | 
 |         unsigned type, size; | 
 |         uint8_t *dst; | 
 |  | 
 |         type = bytestream2_get_le16(gb); | 
 |         size = bytestream2_get_le16(gb); | 
 |         if (size > bytestream2_get_bytes_left(gb)) | 
 |             return AVERROR_INVALIDDATA; | 
 |  | 
 |         bytestream2_init_writer(pb, s->buffer, s->buffer_size); | 
 |         if (type == 1) { | 
 |             decode_type1(gb, pb); | 
 |         } else if (type == 2){ | 
 |             decode_type2(gb, pb); | 
 |         } else { | 
 |             avpriv_report_missing_feature(avctx, "Compression type %d", type); | 
 |             return AVERROR_PATCHWELCOME; | 
 |         } | 
 |  | 
 |         if ((ret = ff_get_buffer(avctx, frame, 0)) < 0) | 
 |             return ret; | 
 |  | 
 |         frame->flags |= AV_FRAME_FLAG_KEY; | 
 |         frame->pict_type = AV_PICTURE_TYPE_I; | 
 |  | 
 |         src = s->buffer; | 
 |         dst = frame->data[0] + (avctx->height - 1) * frame->linesize[0]; | 
 |         for (y = 0; y < avctx->height; y++) { | 
 |             memcpy(dst, src, avctx->width * s->bpp); | 
 |             dst -= frame->linesize[0]; | 
 |             src += s->stride * 4; | 
 |             if (bytestream2_tell_p(pb) < y*s->stride * 4) | 
 |                 break; | 
 |         } | 
 |     } else { | 
 |         unsigned block, nb_blocks; | 
 |         int type, k, l; | 
 |         uint8_t *ssrc, *ddst; | 
 |         const uint32_t *src; | 
 |         uint32_t *dst; | 
 |  | 
 |         for (block = 0; block < s->nb_blocks; block++) | 
 |             s->blocks[block].xor = 0; | 
 |  | 
 |         nb_blocks = bytestream2_get_le16(gb); | 
 |         if (nb_blocks > s->nb_blocks) | 
 |             return AVERROR_INVALIDDATA; | 
 |  | 
 |         bytestream2_init_writer(pb, s->pbuffer, s->pbuffer_size); | 
 |  | 
 |         type = bytestream2_get_le16(gb); | 
 |         for (block = 0; block < nb_blocks; block++) { | 
 |             unsigned size, offset; | 
 |             int start = 0; | 
 |  | 
 |             offset = bytestream2_get_le16(gb); | 
 |             if (offset >= s->nb_blocks) | 
 |                 return AVERROR_INVALIDDATA; | 
 |  | 
 |             size = bytestream2_get_le16(gb); | 
 |             if (size > bytestream2_get_bytes_left(gb)) | 
 |                 return AVERROR_INVALIDDATA; | 
 |  | 
 |             start = bytestream2_tell_p(pb); | 
 |             if (type == 1) { | 
 |                 decode_type1(gb, pb); | 
 |             } else if (type == 2){ | 
 |                 decode_type2(gb, pb); | 
 |             } else { | 
 |                 avpriv_report_missing_feature(avctx, "Compression type %d", type); | 
 |                 return AVERROR_PATCHWELCOME; | 
 |             } | 
 |  | 
 |             if (s->blocks[offset].size * 4 != bytestream2_tell_p(pb) - start) | 
 |                 return AVERROR_INVALIDDATA; | 
 |  | 
 |             s->blocks[offset].xor = 1; | 
 |         } | 
 |  | 
 |         src = (const uint32_t *)s->pbuffer; | 
 |         dst = (uint32_t *)s->buffer; | 
 |  | 
 |         for (block = 0, y = 0; y < s->yb; y++) { | 
 |             int block_h = s->blocks[block].h; | 
 |             uint32_t *rect = dst; | 
 |  | 
 |             for (x = 0; x < s->xb; x++) { | 
 |                 int block_w = s->blocks[block].w; | 
 |                 uint32_t *row = dst; | 
 |  | 
 |                 block_h = s->blocks[block].h; | 
 |                 if (s->blocks[block].xor) { | 
 |                     for (k = 0; k < block_h; k++) { | 
 |                         uint32_t *column = dst; | 
 |                         for (l = 0; l < block_w; l++) | 
 |                             *dst++ ^= *src++; | 
 |                         dst = &column[s->stride]; | 
 |                     } | 
 |                 } | 
 |                 dst = &row[block_w]; | 
 |                 ++block; | 
 |             } | 
 |             dst = &rect[block_h * s->stride]; | 
 |         } | 
 |  | 
 |         if ((ret = ff_get_buffer(avctx, frame, 0)) < 0) | 
 |             return ret; | 
 |  | 
 |         frame->flags &= ~AV_FRAME_FLAG_KEY; | 
 |         frame->pict_type = AV_PICTURE_TYPE_P; | 
 |  | 
 |         ssrc = s->buffer; | 
 |         ddst = frame->data[0] + (avctx->height - 1) * frame->linesize[0]; | 
 |         for (y = 0; y < avctx->height; y++) { | 
 |             memcpy(ddst, ssrc, avctx->width * s->bpp); | 
 |             ddst -= frame->linesize[0]; | 
 |             ssrc += s->stride * 4; | 
 |         } | 
 |     } | 
 |  | 
 |     *got_frame = 1; | 
 |  | 
 |     return avpkt->size; | 
 | } | 
 |  | 
 | static av_cold int decode_init(AVCodecContext *avctx) | 
 | { | 
 |     FMVCContext *s = avctx->priv_data; | 
 |     int i, j, m, block = 0, h = BLOCK_HEIGHT, w = BLOCK_WIDTH; | 
 |  | 
 |     switch (avctx->bits_per_coded_sample) { | 
 |     case 16: | 
 |         avctx->pix_fmt = AV_PIX_FMT_RGB555LE; | 
 |         break; | 
 |     case 24: | 
 |         avctx->pix_fmt = AV_PIX_FMT_BGR24; | 
 |         break; | 
 |     case 32: | 
 |         avctx->pix_fmt = AV_PIX_FMT_BGRA; | 
 |         break; | 
 |     default: | 
 |         av_log(avctx, AV_LOG_ERROR, "Unsupported bitdepth %i\n", | 
 |                avctx->bits_per_coded_sample); | 
 |         return AVERROR_INVALIDDATA; | 
 |     } | 
 |  | 
 |     s->stride = (avctx->width * avctx->bits_per_coded_sample + 31) / 32; | 
 |     s->xb     = s->stride / BLOCK_WIDTH; | 
 |     m         = s->stride % BLOCK_WIDTH; | 
 |     if (m) { | 
 |         if (m < 37) { | 
 |             w = m + BLOCK_WIDTH; | 
 |         } else { | 
 |             w = m; | 
 |             s->xb++; | 
 |         } | 
 |     } | 
 |  | 
 |     s->yb = avctx->height / BLOCK_HEIGHT; | 
 |     m     = avctx->height % BLOCK_HEIGHT; | 
 |     if (m) { | 
 |         if (m < 49) { | 
 |             h = m + BLOCK_HEIGHT; | 
 |         } else { | 
 |             h = m; | 
 |             s->yb++; | 
 |         } | 
 |     } | 
 |  | 
 |     s->nb_blocks = s->xb * s->yb; | 
 |     if (!s->nb_blocks) | 
 |         return AVERROR_INVALIDDATA; | 
 |     s->blocks    = av_calloc(s->nb_blocks, sizeof(*s->blocks)); | 
 |     if (!s->blocks) | 
 |         return AVERROR(ENOMEM); | 
 |  | 
 |     for (i = 0; i < s->yb; i++) { | 
 |         for (j = 0; j < s->xb; j++) { | 
 |             if (i != (s->yb - 1) || j != (s->xb - 1)) { | 
 |                 if (i == s->yb - 1) { | 
 |                     s->blocks[block].w    = BLOCK_WIDTH; | 
 |                     s->blocks[block].h    = h; | 
 |                     s->blocks[block].size = BLOCK_WIDTH * h; | 
 |                 } else if (j == s->xb - 1) { | 
 |                     s->blocks[block].w    = w; | 
 |                     s->blocks[block].h    = BLOCK_HEIGHT; | 
 |                     s->blocks[block].size = BLOCK_HEIGHT * w; | 
 |                 } else { | 
 |                     s->blocks[block].w    = BLOCK_WIDTH; | 
 |                     s->blocks[block].h    = BLOCK_HEIGHT; | 
 |                     s->blocks[block].size = BLOCK_WIDTH * BLOCK_HEIGHT; | 
 |                 } | 
 |             } else { | 
 |                 s->blocks[block].w    = w; | 
 |                 s->blocks[block].h    = h; | 
 |                 s->blocks[block].size = w * h; | 
 |             } | 
 |             block++; | 
 |         } | 
 |     } | 
 |  | 
 |     s->bpp          = avctx->bits_per_coded_sample >> 3; | 
 |     s->buffer_size  = avctx->width * avctx->height * 4; | 
 |     s->pbuffer_size = avctx->width * avctx->height * 4; | 
 |     s->buffer       = av_mallocz(s->buffer_size); | 
 |     s->pbuffer      = av_mallocz(s->pbuffer_size); | 
 |     if (!s->buffer || !s->pbuffer) | 
 |         return AVERROR(ENOMEM); | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static av_cold int decode_close(AVCodecContext *avctx) | 
 | { | 
 |     FMVCContext *s = avctx->priv_data; | 
 |  | 
 |     av_freep(&s->buffer); | 
 |     av_freep(&s->pbuffer); | 
 |     av_freep(&s->blocks); | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | const FFCodec ff_fmvc_decoder = { | 
 |     .p.name           = "fmvc", | 
 |     CODEC_LONG_NAME("FM Screen Capture Codec"), | 
 |     .p.type           = AVMEDIA_TYPE_VIDEO, | 
 |     .p.id             = AV_CODEC_ID_FMVC, | 
 |     .priv_data_size   = sizeof(FMVCContext), | 
 |     .init             = decode_init, | 
 |     .close            = decode_close, | 
 |     FF_CODEC_DECODE_CB(decode_frame), | 
 |     .p.capabilities   = AV_CODEC_CAP_DR1, | 
 |     .caps_internal    = FF_CODEC_CAP_INIT_CLEANUP, | 
 | }; |