# HG changeset patch # User diego # Date 1175979118 0 # Node ID 2af9c2f9b44df58bf6122dda6781967583a2d900 # Parent deacffc2740e253560b4865d19935653bb6c8136 Bethsoft VID demuxer and video decoder patch by Nicholas Tung, ntung ntung com diff -r deacffc2740e -r 2af9c2f9b44d Makefile --- a/Makefile Sat Apr 07 16:03:23 2007 +0000 +++ b/Makefile Sat Apr 07 20:51:58 2007 +0000 @@ -28,6 +28,7 @@ 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 -r deacffc2740e -r 2af9c2f9b44d allformats.c --- a/allformats.c Sat Apr 07 16:03:23 2007 +0000 +++ b/allformats.c Sat Apr 07 20:51:58 2007 +0000 @@ -58,6 +58,7 @@ 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 -r deacffc2740e -r 2af9c2f9b44d allformats.h --- a/allformats.h Sat Apr 07 16:03:23 2007 +0000 +++ b/allformats.h Sat Apr 07 20:51:58 2007 +0000 @@ -32,6 +32,7 @@ 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 -r deacffc2740e -r 2af9c2f9b44d bethsoftvid.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bethsoftvid.c Sat Apr 07 20:51:58 2007 +0000 @@ -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, +};