From 1e6c67599e34348e00ca556ba1ba1537f88ebc96 Mon Sep 17 00:00:00 2001 From: Nicholas Tung Date: Sat, 7 Apr 2007 20:51:58 +0000 Subject: [PATCH] Bethsoft VID demuxer and video decoder patch by Nicholas Tung, ntung ntung com Originally committed as revision 8649 to svn://svn.ffmpeg.org/ffmpeg/trunk --- Changelog | 1 + doc/ffmpeg-doc.texi | 3 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/avcodec.h | 2 + libavcodec/bethsoftvideo.c | 139 ++++++++++++++++++++++ libavcodec/bethsoftvideo.h | 9 ++ libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/allformats.h | 1 + libavformat/bethsoftvid.c | 234 +++++++++++++++++++++++++++++++++++++ 11 files changed, 393 insertions(+) create mode 100644 libavcodec/bethsoftvideo.c create mode 100644 libavcodec/bethsoftvideo.h create mode 100644 libavformat/bethsoftvid.c diff --git a/Changelog b/Changelog index 347acdcdc6..2b64544214 100644 --- a/Changelog +++ b/Changelog @@ -78,6 +78,7 @@ version - Gamecube movie (.THP) playback system - Blackfin optimizations - Interplay C93 demuxer and video decoder +- Bethsoft VID demuxer and video decoder version 0.4.9-pre1: diff --git a/doc/ffmpeg-doc.texi b/doc/ffmpeg-doc.texi index 8c351c6374..55acced8b4 100644 --- a/doc/ffmpeg-doc.texi +++ b/doc/ffmpeg-doc.texi @@ -905,6 +905,8 @@ different game cutscenes repacked for use with ScummVM. @tab Used on the Nintendo GameCube. @item C93 @tab @tab X @tab Used in the game Cyberia from Interplay. +@item Bethsoft VID @tab @tab X +@tab Used in some games from Bethesda Softworks. @end multitable @code{X} means that encoding (resp. decoding) is supported. @@ -1015,6 +1017,7 @@ following image formats are supported: @item DXA Video @tab @tab X @tab Codec originally used in Feeble Files game. @item AVID DNxHD @tab @tab X @tab aka SMPTE VC3 @item C93 Video @tab @tab X @tab Codec used in Cyberia game. +@item Bethsoft VID @tab @tab X @tab Used in some games from Bethesda Softworks. @end multitable @code{X} means that encoding (resp. decoding) is supported. diff --git a/libavcodec/Makefile b/libavcodec/Makefile index a07879e962..f295759674 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -54,6 +54,7 @@ OBJS-$(CONFIG_ASV1_ENCODER) += asv1.o OBJS-$(CONFIG_ASV2_DECODER) += asv1.o OBJS-$(CONFIG_ASV2_ENCODER) += asv1.o OBJS-$(CONFIG_AVS_DECODER) += avs.o +OBJS-$(CONFIG_BETHSOFTVID_DECODER) += bethsoftvideo.o OBJS-$(CONFIG_BMP_DECODER) += bmp.o OBJS-$(CONFIG_BMP_ENCODER) += bmpenc.o OBJS-$(CONFIG_C93_DECODER) += c93.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index be41c147f5..5464dc7abc 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -58,6 +58,7 @@ void avcodec_register_all(void) REGISTER_ENCDEC (ASV1, asv1); REGISTER_ENCDEC (ASV2, asv2); REGISTER_DECODER(AVS, avs); + REGISTER_DECODER(BETHSOFTVID, bethsoftvid); REGISTER_ENCDEC (BMP, bmp); REGISTER_DECODER(C93, c93); REGISTER_DECODER(CAVS, cavs); diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h index 421171a183..9fd2fb31ff 100644 --- a/libavcodec/avcodec.h +++ b/libavcodec/avcodec.h @@ -161,6 +161,7 @@ enum CodecID { CODEC_ID_THP, CODEC_ID_SGI, CODEC_ID_C93, + CODEC_ID_BETHSOFTVID, /* various PCM "codecs" */ CODEC_ID_PCM_S16LE= 0x10000, @@ -2252,6 +2253,7 @@ extern AVCodec amr_wb_decoder; extern AVCodec asv1_decoder; extern AVCodec asv2_decoder; extern AVCodec avs_decoder; +extern AVCodec bethsoftvid_decoder; extern AVCodec bmp_decoder; extern AVCodec c93_decoder; extern AVCodec cavs_decoder; diff --git a/libavcodec/bethsoftvideo.c b/libavcodec/bethsoftvideo.c new file mode 100644 index 0000000000..a59743789a --- /dev/null +++ b/libavcodec/bethsoftvideo.c @@ -0,0 +1,139 @@ +/* + * Bethesda VID video decoder + * Copyright (C) 2007 Nicholas Tung + * + * 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 bethsoftvideo.c + * @brief Bethesda Softworks VID Video Decoder + * @author Nicholas Tung [ntung (at. ntung com] (2007-03) + * @sa http://wiki.multimedia.cx/index.php?title=Bethsoft_VID + * @sa http://www.svatopluk.com/andux/docs/dfvid.html + */ + +#include "common.h" +#include "dsputil.h" +#include "bethsoftvideo.h" +#include "bytestream.h" + +typedef struct BethsoftvidContext { + AVFrame frame; +} BethsoftvidContext; + +static int bethsoftvid_decode_init(AVCodecContext *avctx) +{ + BethsoftvidContext *vid = avctx->priv_data; + vid->frame.reference = 1; + vid->frame.buffer_hints = FF_BUFFER_HINTS_VALID | + FF_BUFFER_HINTS_PRESERVE | FF_BUFFER_HINTS_REUSABLE; + avctx->pix_fmt = PIX_FMT_PAL8; // palette in vid->frame.data[1] + av_log(avctx, AV_LOG_DEBUG, "[bethsoftvid video decoder] init\n"); + return 0; +} + +static void set_palette(AVFrame * frame, uint8_t * palette_buffer) +{ + uint32_t * palette = (uint32_t *)frame->data[1]; + int a; + for(a = 0; a < VID_PALETTE_NUMCOLORS; a++) + { + palette[a] = AV_RB24(&palette_buffer[a * 3]) * 4; // multiply all colors by 4 + } + frame->palette_has_changed = 1; +} + +static int bethsoftvid_decode_frame(AVCodecContext *avctx, + void *data, int *data_size, + uint8_t *buf, int buf_size) +{ + BethsoftvidContext * vid = avctx->priv_data; + char block_type; + uint8_t * destination; + uint8_t * frame_end; + int line_remaining = avctx->width; // number of bytes remaining on a line + const int wrap_to_next_line = vid->frame.linesize[0] - avctx->width; + uint8_t rle_num_bytes; + int yoffset; + + av_log(avctx, AV_LOG_DEBUG, "[bethsoftvid video decoder] decoding frame\n"); + + // reget buffer will copy old data, good for simple difference frames + if (avctx->reget_buffer(avctx, &vid->frame)) { + av_log(avctx, AV_LOG_ERROR, "reget_buffer() failed\n"); + return -1; + } + destination = vid->frame.data[0]; + frame_end = vid->frame.data[0] + vid->frame.linesize[0] * avctx->height; + + switch(block_type = *buf++) + { + case PALETTE_BLOCK: set_palette(&vid->frame, buf); return 0; + case VIDEO_YOFFSET_DIFFERENCE_FRAME_BLOCK: + yoffset = bytestream_get_le16(&buf); + if(yoffset >= avctx->height) { return -1; } + destination += vid->frame.linesize[0] * yoffset; + } + + // main code + while((rle_num_bytes = *buf++)) + { + int length = rle_num_bytes & 0x7f; + + // copy any bytes starting at the current position, and ending at the frame width + while(length > line_remaining) + { + if(rle_num_bytes < 0x80) { bytestream_get_buffer(&buf, destination, line_remaining); } + else if(block_type == VIDEO_FULL_FRAME_BLOCK) { memset(destination, buf[0], line_remaining); } + length -= line_remaining; // decrement the number of bytes to be copied + destination += line_remaining + wrap_to_next_line; // skip over extra bytes at end of frame + line_remaining = avctx->width; + if(destination == frame_end) { goto end; } + } + + // copy any remaining bytes after / if line overflows + if(rle_num_bytes < 0x80) { bytestream_get_buffer(&buf, destination, length); } + else if(block_type == VIDEO_FULL_FRAME_BLOCK) { memset(destination, *buf++, length); } + line_remaining -= length; + destination += length; + } + end: + + *data_size = sizeof(AVFrame); + *(AVFrame*)data = vid->frame; + + return buf_size; +} + +static int bethsoftvid_decode_end(AVCodecContext *avctx) +{ + BethsoftvidContext * vid = avctx->priv_data; + av_log(avctx, AV_LOG_DEBUG, "[bethsoftvid video decoder] closing\n"); + if(vid->frame.data[0]) { avctx->release_buffer(avctx, &vid->frame); } + return 0; +} + +AVCodec bethsoftvid_decoder = { + .name = "bethsoftvid", + .type = CODEC_TYPE_VIDEO, + .id = CODEC_ID_BETHSOFTVID, + .priv_data_size = sizeof(BethsoftvidContext), + .init = bethsoftvid_decode_init, + .close = bethsoftvid_decode_end, + .decode = bethsoftvid_decode_frame, +}; diff --git a/libavcodec/bethsoftvideo.h b/libavcodec/bethsoftvideo.h new file mode 100644 index 0000000000..036175af95 --- /dev/null +++ b/libavcodec/bethsoftvideo.h @@ -0,0 +1,9 @@ +#define VID_PALETTE_NUMCOLORS 256 + +enum BethsoftVidBlockType +{ + PALETTE_BLOCK = 0x02, + FIRST_AUDIO_BLOCK = 0x7c, AUDIO_BLOCK = 0x7d, + VIDEO_FULL_FRAME_BLOCK = 0x03, VIDEO_DIFFERENCE_FRAME_BLOCK = 0x01, VIDEO_YOFFSET_DIFFERENCE_FRAME_BLOCK = 0x04, + FINISHED_BLOCK = 0x14, +}; diff --git a/libavformat/Makefile b/libavformat/Makefile index 4e18f72657..b58069b532 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -28,6 +28,7 @@ OBJS-$(CONFIG_AVI_DEMUXER) += avidec.o riff.o OBJS-$(CONFIG_AVI_MUXER) += avienc.o riff.o OBJS-$(CONFIG_AVISYNTH) += avisynth.o OBJS-$(CONFIG_AVS_DEMUXER) += avs.o vocdec.o voc.o riff.o +OBJS-$(CONFIG_BETHSOFTVID_DEMUXER) += bethsoftvid.o OBJS-$(CONFIG_C93_DEMUXER) += c93.o OBJS-$(CONFIG_CRC_MUXER) += crc.o OBJS-$(CONFIG_FRAMECRC_MUXER) += crc.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 3cfe872661..9e1f76533e 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -58,6 +58,7 @@ void av_register_all(void) av_register_input_format(&avisynth_demuxer); #endif REGISTER_DEMUXER (AVS, avs); + REGISTER_DEMUXER (BETHSOFTVID, bethsoftvid); REGISTER_DEMUXER (C93, c93); REGISTER_MUXER (CRC, crc); REGISTER_DEMUXER (DAUD, daud); diff --git a/libavformat/allformats.h b/libavformat/allformats.h index 3e0cb6b980..a3ba464376 100644 --- a/libavformat/allformats.h +++ b/libavformat/allformats.h @@ -32,6 +32,7 @@ extern AVInputFormat audio_demuxer; extern AVInputFormat avi_demuxer; extern AVInputFormat avisynth_demuxer; extern AVInputFormat avs_demuxer; +extern AVInputFormat bethsoftvid_demuxer; extern AVInputFormat c93_demuxer; extern AVInputFormat daud_demuxer; extern AVInputFormat dc1394_demuxer; diff --git a/libavformat/bethsoftvid.c b/libavformat/bethsoftvid.c new file mode 100644 index 0000000000..7ea483505d --- /dev/null +++ b/libavformat/bethsoftvid.c @@ -0,0 +1,234 @@ +/* + * Bethsoft VID format Demuxer + * Copyright (c) 2007 Nicholas Tung + * + * 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 bethsoftvid.c + * @brief Bethesda Softworks VID (.vid) file demuxer + * @author Nicholas Tung [ntung (at. ntung com] (2007-03) + * @sa http://wiki.multimedia.cx/index.php?title=Bethsoft_VID + * @sa http://www.svatopluk.com/andux/docs/dfvid.html + */ + +#include "avformat.h" +#include "bethsoftvideo.h" + +typedef struct BVID_DemuxContext +{ + int nframes; + /** delay value between frames, added to individual frame delay. + * custom units, which will be added to other custom units (~=16ms according + * to free, unofficial documentation) */ + int bethsoft_global_delay; + + /** video presentation time stamp. + * delay = 16 milliseconds * (global_delay + per_frame_delay) */ + int64_t video_pts; + + int is_finished; + +} BVID_DemuxContext; + +static int vid_probe(AVProbeData *p) +{ + // little endian VID tag, file starts with "VID\0" + if (p->buf_size < 4 || AV_RL32(p->buf) != MKTAG('V', 'I', 'D', 0)) + return 0; + + return AVPROBE_SCORE_MAX; +} + +static int vid_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + BVID_DemuxContext *vid = s->priv_data; // permanent data outside of function + ByteIOContext *pb = &s->pb; // io to file + AVStream *stream; + + /* load main header. Contents: + * bytes: 'V' 'I' 'D' + * int16s: always_512, nframes, width, height, delay, always_14 + */ + url_fseek(pb, 5, SEEK_CUR); + vid->nframes = get_le16(pb); + + // FFmpeg central code will use this; don't need to return or anything + // initialize the bethsoft codec + stream = av_new_stream(s, 0); + if (!stream) { return AVERROR_NOMEM; } + av_set_pts_info(stream, 32, 1, 60); // 16 ms increments, i.e. 60 fps + stream->codec->codec_type = CODEC_TYPE_VIDEO; + stream->codec->codec_id = CODEC_ID_BETHSOFTVID; + stream->codec->width = get_le16(pb); + stream->codec->height = get_le16(pb); + stream->codec->pix_fmt = PIX_FMT_PAL8; + vid->bethsoft_global_delay = get_le16(pb); + get_le16(pb); + + // done with video codec, set up audio codec + stream = av_new_stream(s, 0); + if (!stream) { return AVERROR_NOMEM; } + stream->codec->codec_type = CODEC_TYPE_AUDIO; + stream->codec->codec_id = CODEC_ID_PCM_U8; + stream->codec->channels = 1; + stream->codec->sample_rate = 11025; + stream->codec->bits_per_sample = 8; + stream->codec->bit_rate = stream->codec->channels * stream->codec->sample_rate * stream->codec->bits_per_sample; + + return 0; +} + +#define BUFFER_PADDING_SIZE 1000 +static int read_frame(BVID_DemuxContext *vid, ByteIOContext *pb, AVPacket *pkt, + uint8_t block_type, AVFormatContext *s, int npixels) +{ + uint8_t * vidbuf_start = NULL; + int vidbuf_nbytes = 0; + int rle_num_bytes; + int bytes_copied = 0; + int position; + size_t vidbuf_capacity; + + vidbuf_start = av_malloc(vidbuf_capacity = BUFFER_PADDING_SIZE); + if(!vidbuf_start) { return AVERROR_NOMEM; } + + // save the file position for the packet, include block type + position = url_ftell(pb) - 1; + + // set the block type for the decoder + vidbuf_start[vidbuf_nbytes++] = block_type; + + // get the video delay (next int16), and set the presentation time + vid->video_pts += vid->bethsoft_global_delay + get_le16(pb); + + // set the y offset if it exists (decoder header data should be in data section) + if(block_type == VIDEO_YOFFSET_DIFFERENCE_FRAME_BLOCK) + { + if(get_buffer(pb, &vidbuf_start[vidbuf_nbytes], 2) != 2) { return AVERROR_IO; } + vidbuf_nbytes += 2; + } + + do + { + vidbuf_start = av_fast_realloc(vidbuf_start, &vidbuf_capacity, vidbuf_nbytes + BUFFER_PADDING_SIZE); + if(!vidbuf_start) { return AVERROR_NOMEM; } + + rle_num_bytes = get_byte(pb); + vidbuf_start[vidbuf_nbytes++] = rle_num_bytes; + + if(rle_num_bytes > 0x80) // rle sequence + { + if(block_type == VIDEO_FULL_FRAME_BLOCK) { vidbuf_start[vidbuf_nbytes++] = get_byte(pb); } + bytes_copied += rle_num_bytes - 0x80; + } + else if(rle_num_bytes) // plain sequence + { + if(get_buffer(pb, &vidbuf_start[vidbuf_nbytes], rle_num_bytes) != rle_num_bytes) + return AVERROR_IO; + vidbuf_nbytes += rle_num_bytes; + bytes_copied += rle_num_bytes; + } + if(bytes_copied == npixels) // sometimes no stop character is given, need to keep track of bytes copied + { + // may contain a 0 byte even if read all pixels + if(get_byte(pb)) { url_fseek(pb, -1, SEEK_CUR); } + break; + } + if(bytes_copied > npixels) { return -1; } // error + } while(rle_num_bytes); + + // copy data into packet + if(av_new_packet(pkt, vidbuf_nbytes)) { return AVERROR_NOMEM; } + memcpy(pkt->data, vidbuf_start, vidbuf_nbytes); + av_free(vidbuf_start); + + pkt->pos = position; + pkt->stream_index = 0; // use the video decoder, which was initialized as the first stream + pkt->pts = vid->video_pts; + + vid->nframes--; // used to check if all the frames were read + return vidbuf_nbytes; +} + +static int vid_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + BVID_DemuxContext *vid = s->priv_data; // permanent data outside of function + ByteIOContext *pb = &s->pb; // io to file + unsigned char block_type; // block type + int audio_length; + int ret_value; + + av_log(s, AV_LOG_DEBUG, "[bethsoftvid demuxer read_packet]"); + + if(vid->is_finished || url_feof(pb)) { return AVERROR_IO; } + + block_type = get_byte(pb); + switch(block_type) + { + case PALETTE_BLOCK: + av_log(s, AV_LOG_DEBUG, "palette block.\n"); + url_fseek(pb, -1, SEEK_CUR); // include block type + ret_value = av_get_packet(pb, pkt, 3 * VID_PALETTE_NUMCOLORS + 1); + if(ret_value != 3 * VID_PALETTE_NUMCOLORS + 1) { av_free_packet(pkt); return AVERROR_IO; } + pkt->stream_index = 0; + return ret_value; + + case FIRST_AUDIO_BLOCK: + av_log(s, AV_LOG_DEBUG, "first "); + get_le16(pb); // some unused constant + // soundblaster DAC used for sample rate, as on specification page (link above) + s->streams[1]->codec->sample_rate = 1000000 / (256 - get_byte(pb)); + s->streams[1]->codec->bit_rate = s->streams[1]->codec->channels * s->streams[1]->codec->sample_rate * s->streams[1]->codec->bits_per_sample; + case AUDIO_BLOCK: + av_log(s, AV_LOG_DEBUG, "audio block.\n"); + audio_length = get_le16(pb); + ret_value = av_get_packet(pb, pkt, audio_length); + pkt->stream_index = 1; + return (ret_value != audio_length ? AVERROR_IO : ret_value); + + case VIDEO_DIFFERENCE_FRAME_BLOCK: av_log(s, AV_LOG_DEBUG, "non-"); + case VIDEO_YOFFSET_DIFFERENCE_FRAME_BLOCK: av_log(s, AV_LOG_DEBUG, "offset "); + case VIDEO_FULL_FRAME_BLOCK: av_log(s, AV_LOG_DEBUG, "video block.\n"); + return read_frame(vid, pb, pkt, block_type, s, + s->streams[0]->codec->width * s->streams[0]->codec->height); + + case FINISHED_BLOCK: + if(vid->nframes != 0) + av_log(s, AV_LOG_VERBOSE, "reached terminating character but not all frames read.\n"); + vid->is_finished = 1; + av_log(s, AV_LOG_DEBUG, "terminating block.\n"); + return AVERROR_IO; + default: + av_log(s, AV_LOG_ERROR, "unknown block (character = %c, decimal = %d, hex = %x)!!!\n", + block_type, block_type, block_type); return -1; + } + + return 0; +} + +AVInputFormat bethsoftvid_demuxer = { + "bethsoftvid", + "Bethesda Softworks 'Daggerfall' VID format", + sizeof(BVID_DemuxContext), + vid_probe, + vid_read_header, + vid_read_packet, +};