yading@10: /* yading@10: * Quicktime Video (RPZA) Video Decoder yading@10: * Copyright (C) 2003 the ffmpeg project yading@10: * yading@10: * This file is part of FFmpeg. yading@10: * yading@10: * FFmpeg is free software; you can redistribute it and/or yading@10: * modify it under the terms of the GNU Lesser General Public yading@10: * License as published by the Free Software Foundation; either yading@10: * version 2.1 of the License, or (at your option) any later version. yading@10: * yading@10: * FFmpeg is distributed in the hope that it will be useful, yading@10: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@10: * Lesser General Public License for more details. yading@10: * yading@10: * You should have received a copy of the GNU Lesser General Public yading@10: * License along with FFmpeg; if not, write to the Free Software yading@10: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@10: */ yading@10: yading@10: /** yading@10: * @file yading@10: * QT RPZA Video Decoder by Roberto Togni yading@10: * For more information about the RPZA format, visit: yading@10: * http://www.pcisys.net/~melanson/codecs/ yading@10: * yading@10: * The RPZA decoder outputs RGB555 colorspace data. yading@10: * yading@10: * Note that this decoder reads big endian RGB555 pixel values from the yading@10: * bytestream, arranges them in the host's endian order, and outputs yading@10: * them to the final rendered map in the same host endian order. This is yading@10: * intended behavior as the libavcodec documentation states that RGB555 yading@10: * pixels shall be stored in native CPU endianness. yading@10: */ yading@10: yading@10: #include yading@10: #include yading@10: #include yading@10: yading@10: #include "libavutil/internal.h" yading@10: #include "libavutil/intreadwrite.h" yading@10: #include "avcodec.h" yading@10: #include "internal.h" yading@10: yading@10: typedef struct RpzaContext { yading@10: yading@10: AVCodecContext *avctx; yading@10: AVFrame frame; yading@10: yading@10: const unsigned char *buf; yading@10: int size; yading@10: yading@10: } RpzaContext; yading@10: yading@10: #define ADVANCE_BLOCK() \ yading@10: { \ yading@10: pixel_ptr += 4; \ yading@10: if (pixel_ptr >= width) \ yading@10: { \ yading@10: pixel_ptr = 0; \ yading@10: row_ptr += stride * 4; \ yading@10: } \ yading@10: total_blocks--; \ yading@10: if (total_blocks < 0) \ yading@10: { \ yading@10: av_log(s->avctx, AV_LOG_ERROR, "warning: block counter just went negative (this should not happen)\n"); \ yading@10: return; \ yading@10: } \ yading@10: } yading@10: yading@10: static void rpza_decode_stream(RpzaContext *s) yading@10: { yading@10: int width = s->avctx->width; yading@10: int stride = s->frame.linesize[0] / 2; yading@10: int row_inc = stride - 4; yading@10: int stream_ptr = 0; yading@10: int chunk_size; yading@10: unsigned char opcode; yading@10: int n_blocks; yading@10: unsigned short colorA = 0, colorB; yading@10: unsigned short color4[4]; yading@10: unsigned char index, idx; yading@10: unsigned short ta, tb; yading@10: unsigned short *pixels = (unsigned short *)s->frame.data[0]; yading@10: yading@10: int row_ptr = 0; yading@10: int pixel_ptr = 0; yading@10: int block_ptr; yading@10: int pixel_x, pixel_y; yading@10: int total_blocks; yading@10: yading@10: /* First byte is always 0xe1. Warn if it's different */ yading@10: if (s->buf[stream_ptr] != 0xe1) yading@10: av_log(s->avctx, AV_LOG_ERROR, "First chunk byte is 0x%02x instead of 0xe1\n", yading@10: s->buf[stream_ptr]); yading@10: yading@10: /* Get chunk size, ingnoring first byte */ yading@10: chunk_size = AV_RB32(&s->buf[stream_ptr]) & 0x00FFFFFF; yading@10: stream_ptr += 4; yading@10: yading@10: /* If length mismatch use size from MOV file and try to decode anyway */ yading@10: if (chunk_size != s->size) yading@10: av_log(s->avctx, AV_LOG_ERROR, "MOV chunk size != encoded chunk size; using MOV chunk size\n"); yading@10: yading@10: chunk_size = s->size; yading@10: yading@10: /* Number of 4x4 blocks in frame. */ yading@10: total_blocks = ((s->avctx->width + 3) / 4) * ((s->avctx->height + 3) / 4); yading@10: yading@10: /* Process chunk data */ yading@10: while (stream_ptr < chunk_size) { yading@10: opcode = s->buf[stream_ptr++]; /* Get opcode */ yading@10: yading@10: n_blocks = (opcode & 0x1f) + 1; /* Extract block counter from opcode */ yading@10: yading@10: /* If opcode MSbit is 0, we need more data to decide what to do */ yading@10: if ((opcode & 0x80) == 0) { yading@10: colorA = (opcode << 8) | (s->buf[stream_ptr++]); yading@10: opcode = 0; yading@10: if ((s->buf[stream_ptr] & 0x80) != 0) { yading@10: /* Must behave as opcode 110xxxxx, using colorA computed yading@10: * above. Use fake opcode 0x20 to enter switch block at yading@10: * the right place */ yading@10: opcode = 0x20; yading@10: n_blocks = 1; yading@10: } yading@10: } yading@10: yading@10: switch (opcode & 0xe0) { yading@10: yading@10: /* Skip blocks */ yading@10: case 0x80: yading@10: while (n_blocks--) { yading@10: ADVANCE_BLOCK(); yading@10: } yading@10: break; yading@10: yading@10: /* Fill blocks with one color */ yading@10: case 0xa0: yading@10: colorA = AV_RB16 (&s->buf[stream_ptr]); yading@10: stream_ptr += 2; yading@10: while (n_blocks--) { yading@10: block_ptr = row_ptr + pixel_ptr; yading@10: for (pixel_y = 0; pixel_y < 4; pixel_y++) { yading@10: for (pixel_x = 0; pixel_x < 4; pixel_x++){ yading@10: pixels[block_ptr] = colorA; yading@10: block_ptr++; yading@10: } yading@10: block_ptr += row_inc; yading@10: } yading@10: ADVANCE_BLOCK(); yading@10: } yading@10: break; yading@10: yading@10: /* Fill blocks with 4 colors */ yading@10: case 0xc0: yading@10: colorA = AV_RB16 (&s->buf[stream_ptr]); yading@10: stream_ptr += 2; yading@10: case 0x20: yading@10: colorB = AV_RB16 (&s->buf[stream_ptr]); yading@10: stream_ptr += 2; yading@10: yading@10: /* sort out the colors */ yading@10: color4[0] = colorB; yading@10: color4[1] = 0; yading@10: color4[2] = 0; yading@10: color4[3] = colorA; yading@10: yading@10: /* red components */ yading@10: ta = (colorA >> 10) & 0x1F; yading@10: tb = (colorB >> 10) & 0x1F; yading@10: color4[1] |= ((11 * ta + 21 * tb) >> 5) << 10; yading@10: color4[2] |= ((21 * ta + 11 * tb) >> 5) << 10; yading@10: yading@10: /* green components */ yading@10: ta = (colorA >> 5) & 0x1F; yading@10: tb = (colorB >> 5) & 0x1F; yading@10: color4[1] |= ((11 * ta + 21 * tb) >> 5) << 5; yading@10: color4[2] |= ((21 * ta + 11 * tb) >> 5) << 5; yading@10: yading@10: /* blue components */ yading@10: ta = colorA & 0x1F; yading@10: tb = colorB & 0x1F; yading@10: color4[1] |= ((11 * ta + 21 * tb) >> 5); yading@10: color4[2] |= ((21 * ta + 11 * tb) >> 5); yading@10: yading@10: if (s->size - stream_ptr < n_blocks * 4) yading@10: return; yading@10: while (n_blocks--) { yading@10: block_ptr = row_ptr + pixel_ptr; yading@10: for (pixel_y = 0; pixel_y < 4; pixel_y++) { yading@10: index = s->buf[stream_ptr++]; yading@10: for (pixel_x = 0; pixel_x < 4; pixel_x++){ yading@10: idx = (index >> (2 * (3 - pixel_x))) & 0x03; yading@10: pixels[block_ptr] = color4[idx]; yading@10: block_ptr++; yading@10: } yading@10: block_ptr += row_inc; yading@10: } yading@10: ADVANCE_BLOCK(); yading@10: } yading@10: break; yading@10: yading@10: /* Fill block with 16 colors */ yading@10: case 0x00: yading@10: if (s->size - stream_ptr < 16) yading@10: return; yading@10: block_ptr = row_ptr + pixel_ptr; yading@10: for (pixel_y = 0; pixel_y < 4; pixel_y++) { yading@10: for (pixel_x = 0; pixel_x < 4; pixel_x++){ yading@10: /* We already have color of upper left pixel */ yading@10: if ((pixel_y != 0) || (pixel_x !=0)) { yading@10: colorA = AV_RB16 (&s->buf[stream_ptr]); yading@10: stream_ptr += 2; yading@10: } yading@10: pixels[block_ptr] = colorA; yading@10: block_ptr++; yading@10: } yading@10: block_ptr += row_inc; yading@10: } yading@10: ADVANCE_BLOCK(); yading@10: break; yading@10: yading@10: /* Unknown opcode */ yading@10: default: yading@10: av_log(s->avctx, AV_LOG_ERROR, "Unknown opcode %d in rpza chunk." yading@10: " Skip remaining %d bytes of chunk data.\n", opcode, yading@10: chunk_size - stream_ptr); yading@10: return; yading@10: } /* Opcode switch */ yading@10: } yading@10: } yading@10: yading@10: static av_cold int rpza_decode_init(AVCodecContext *avctx) yading@10: { yading@10: RpzaContext *s = avctx->priv_data; yading@10: yading@10: s->avctx = avctx; yading@10: avctx->pix_fmt = AV_PIX_FMT_RGB555; yading@10: yading@10: avcodec_get_frame_defaults(&s->frame); yading@10: yading@10: return 0; yading@10: } yading@10: yading@10: static int rpza_decode_frame(AVCodecContext *avctx, yading@10: void *data, int *got_frame, yading@10: AVPacket *avpkt) yading@10: { yading@10: const uint8_t *buf = avpkt->data; yading@10: int buf_size = avpkt->size; yading@10: RpzaContext *s = avctx->priv_data; yading@10: int ret; yading@10: yading@10: s->buf = buf; yading@10: s->size = buf_size; yading@10: yading@10: if ((ret = ff_reget_buffer(avctx, &s->frame)) < 0) yading@10: return ret; yading@10: yading@10: rpza_decode_stream(s); yading@10: yading@10: if ((ret = av_frame_ref(data, &s->frame)) < 0) yading@10: return ret; yading@10: yading@10: *got_frame = 1; yading@10: yading@10: /* always report that the buffer was completely consumed */ yading@10: return buf_size; yading@10: } yading@10: yading@10: static av_cold int rpza_decode_end(AVCodecContext *avctx) yading@10: { yading@10: RpzaContext *s = avctx->priv_data; yading@10: yading@10: av_frame_unref(&s->frame); yading@10: yading@10: return 0; yading@10: } yading@10: yading@10: AVCodec ff_rpza_decoder = { yading@10: .name = "rpza", yading@10: .type = AVMEDIA_TYPE_VIDEO, yading@10: .id = AV_CODEC_ID_RPZA, yading@10: .priv_data_size = sizeof(RpzaContext), yading@10: .init = rpza_decode_init, yading@10: .close = rpza_decode_end, yading@10: .decode = rpza_decode_frame, yading@10: .capabilities = CODEC_CAP_DR1, yading@10: .long_name = NULL_IF_CONFIG_SMALL("QuickTime video (RPZA)"), yading@10: };