westwood_vqa.c 9.1 KB
Newer Older
1
/*
2
 * Westwood Studios VQA Format Demuxer
3 4
 * Copyright (c) 2003 The ffmpeg Project
 *
5 6 7
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
8 9
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * FFmpeg is distributed in the hope that it will be useful,
13 14 15 16 17
 * 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
18
 * License along with FFmpeg; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 21 22
 */

/**
23
 * @file
24
 * Westwood Studios VQA file demuxer
25 26 27 28 29 30
 * by Mike Melanson (melanson@pcisys.net)
 * for more information on the Westwood file formats, visit:
 *   http://www.pcisys.net/~melanson/codecs/
 *   http://www.geocities.com/SiliconValley/8682/aud3.txt
 */

31
#include "libavutil/intreadwrite.h"
32
#include "avformat.h"
33
#include "internal.h"
34

35 36 37 38 39
#define FORM_TAG MKBETAG('F', 'O', 'R', 'M')
#define WVQA_TAG MKBETAG('W', 'V', 'Q', 'A')
#define VQHD_TAG MKBETAG('V', 'Q', 'H', 'D')
#define FINF_TAG MKBETAG('F', 'I', 'N', 'F')
#define SND0_TAG MKBETAG('S', 'N', 'D', '0')
Kostya Shishkov's avatar
Kostya Shishkov committed
40
#define SND1_TAG MKBETAG('S', 'N', 'D', '1')
41 42
#define SND2_TAG MKBETAG('S', 'N', 'D', '2')
#define VQFR_TAG MKBETAG('V', 'Q', 'F', 'R')
43

44
/* don't know what these tags are for, but acknowledge their existence */
45 46 47 48 49 50
#define CINF_TAG MKBETAG('C', 'I', 'N', 'F')
#define CINH_TAG MKBETAG('C', 'I', 'N', 'H')
#define CIND_TAG MKBETAG('C', 'I', 'N', 'D')
#define PINF_TAG MKBETAG('P', 'I', 'N', 'F')
#define PINH_TAG MKBETAG('P', 'I', 'N', 'H')
#define PIND_TAG MKBETAG('P', 'I', 'N', 'D')
Kostya Shishkov's avatar
Kostya Shishkov committed
51
#define CMDS_TAG MKBETAG('C', 'M', 'D', 'S')
52

53 54 55 56
#define VQA_HEADER_SIZE 0x2A
#define VQA_PREAMBLE_SIZE 8

typedef struct WsVqaDemuxContext {
57 58 59 60
    int version;
    int bps;
    int channels;
    int sample_rate;
61 62 63 64 65 66 67 68 69 70 71
    int audio_stream_index;
    int video_stream_index;
} WsVqaDemuxContext;

static int wsvqa_probe(AVProbeData *p)
{
    /* need 12 bytes to qualify */
    if (p->buf_size < 12)
        return 0;

    /* check for the VQA signatures */
72 73
    if ((AV_RB32(&p->buf[0]) != FORM_TAG) ||
        (AV_RB32(&p->buf[8]) != WVQA_TAG))
74 75 76 77 78
        return 0;

    return AVPROBE_SCORE_MAX;
}

79
static int wsvqa_read_header(AVFormatContext *s)
80
{
81
    WsVqaDemuxContext *wsvqa = s->priv_data;
82
    AVIOContext *pb = s->pb;
83 84 85
    AVStream *st;
    unsigned char *header;
    unsigned char scratch[VQA_PREAMBLE_SIZE];
86 87
    unsigned int chunk_tag;
    unsigned int chunk_size;
88
    int fps;
89 90

    /* initialize the video decoder stream */
91
    st = avformat_new_stream(s, NULL);
92
    if (!st)
93
        return AVERROR(ENOMEM);
94
    st->start_time = 0;
95
    wsvqa->video_stream_index = st->index;
96
    st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
97
    st->codec->codec_id = AV_CODEC_ID_WS_VQA;
98
    st->codec->codec_tag = 0;  /* no fourcc */
99 100

    /* skip to the start of the VQA header */
101
    avio_seek(pb, 20, SEEK_SET);
102 103

    /* the VQA header needs to go to the decoder */
104
    st->codec->extradata_size = VQA_HEADER_SIZE;
105
    st->codec->extradata = av_mallocz(VQA_HEADER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE);
106
    header = (unsigned char *)st->codec->extradata;
107
    if (avio_read(pb, st->codec->extradata, VQA_HEADER_SIZE) !=
108
        VQA_HEADER_SIZE) {
109
        av_free(st->codec->extradata);
110
        return AVERROR(EIO);
111
    }
112 113
    st->codec->width = AV_RL16(&header[6]);
    st->codec->height = AV_RL16(&header[8]);
114
    fps = header[12];
115 116
    st->nb_frames =
    st->duration  = AV_RL16(&header[4]);
117 118 119 120 121
    if (fps < 1 || fps > 30) {
        av_log(s, AV_LOG_ERROR, "invalid fps: %d\n", fps);
        return AVERROR_INVALIDDATA;
    }
    avpriv_set_pts_info(st, 64, 1, fps);
122

123 124 125 126 127
    wsvqa->version      = AV_RL16(&header[ 0]);
    wsvqa->sample_rate  = AV_RL16(&header[24]);
    wsvqa->channels     = header[26];
    wsvqa->bps          = header[27];
    wsvqa->audio_stream_index = -1;
128

129
    s->ctx_flags |= AVFMTCTX_NOHEADER;
130

131 132 133
    /* there are 0 or more chunks before the FINF chunk; iterate until
     * FINF has been skipped and the file will be ready to be demuxed */
    do {
134
        if (avio_read(pb, scratch, VQA_PREAMBLE_SIZE) != VQA_PREAMBLE_SIZE)
135
            return AVERROR(EIO);
136 137
        chunk_tag = AV_RB32(&scratch[0]);
        chunk_size = AV_RB32(&scratch[4]);
138 139 140 141 142 143 144 145 146 147

        /* catch any unknown header tags, for curiousity */
        switch (chunk_tag) {
        case CINF_TAG:
        case CINH_TAG:
        case CIND_TAG:
        case PINF_TAG:
        case PINH_TAG:
        case PIND_TAG:
        case FINF_TAG:
Kostya Shishkov's avatar
Kostya Shishkov committed
148
        case CMDS_TAG:
149 150 151
            break;

        default:
152
            av_log (s, AV_LOG_ERROR, " note: unknown chunk seen (%c%c%c%c)\n",
153 154 155 156 157
                scratch[0], scratch[1],
                scratch[2], scratch[3]);
            break;
        }

158
        avio_skip(pb, chunk_size);
159
    } while (chunk_tag != FINF_TAG);
160 161 162 163 164 165 166

    return 0;
}

static int wsvqa_read_packet(AVFormatContext *s,
                             AVPacket *pkt)
{
167
    WsVqaDemuxContext *wsvqa = s->priv_data;
168
    AVIOContext *pb = s->pb;
Kostya Shishkov's avatar
Kostya Shishkov committed
169
    int ret = -1;
170 171 172 173 174
    unsigned char preamble[VQA_PREAMBLE_SIZE];
    unsigned int chunk_type;
    unsigned int chunk_size;
    int skip_byte;

175
    while (avio_read(pb, preamble, VQA_PREAMBLE_SIZE) == VQA_PREAMBLE_SIZE) {
176 177
        chunk_type = AV_RB32(&preamble[0]);
        chunk_size = AV_RB32(&preamble[4]);
178

Kostya Shishkov's avatar
Kostya Shishkov committed
179 180
        skip_byte = chunk_size & 0x01;

181 182
        if ((chunk_type == SND0_TAG) || (chunk_type == SND1_TAG) ||
            (chunk_type == SND2_TAG) || (chunk_type == VQFR_TAG)) {
Kostya Shishkov's avatar
Kostya Shishkov committed
183

184 185
            ret= av_get_packet(pb, pkt, chunk_size);
            if (ret<0)
186
                return AVERROR(EIO);
Kostya Shishkov's avatar
Kostya Shishkov committed
187

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
            switch (chunk_type) {
            case SND0_TAG:
            case SND1_TAG:
            case SND2_TAG:
                if (wsvqa->audio_stream_index == -1) {
                    AVStream *st = avformat_new_stream(s, NULL);
                    if (!st)
                        return AVERROR(ENOMEM);

                    wsvqa->audio_stream_index = st->index;
                    if (!wsvqa->sample_rate)
                        wsvqa->sample_rate = 22050;
                    if (!wsvqa->channels)
                        wsvqa->channels = 1;
                    if (!wsvqa->bps)
                        wsvqa->bps = 8;
                    st->codec->sample_rate = wsvqa->sample_rate;
                    st->codec->bits_per_coded_sample = wsvqa->bps;
                    st->codec->channels = wsvqa->channels;
                    st->codec->codec_type = AVMEDIA_TYPE_AUDIO;

                    avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate);

                    switch (chunk_type) {
                    case SND0_TAG:
                        if (wsvqa->bps == 16)
214
                            st->codec->codec_id = AV_CODEC_ID_PCM_S16LE;
215
                        else
216
                            st->codec->codec_id = AV_CODEC_ID_PCM_U8;
217 218
                        break;
                    case SND1_TAG:
219
                        st->codec->codec_id = AV_CODEC_ID_WESTWOOD_SND1;
220 221
                        break;
                    case SND2_TAG:
222
                        st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WS;
223 224 225 226 227 228 229 230 231
                        st->codec->extradata_size = 2;
                        st->codec->extradata = av_mallocz(2 + FF_INPUT_BUFFER_PADDING_SIZE);
                        if (!st->codec->extradata)
                            return AVERROR(ENOMEM);
                        AV_WL16(st->codec->extradata, wsvqa->version);
                        break;
                    }
                }

Kostya Shishkov's avatar
Kostya Shishkov committed
232
                pkt->stream_index = wsvqa->audio_stream_index;
233 234 235
                switch (chunk_type) {
                case SND1_TAG:
                    /* unpacked size is stored in header */
236 237
                    if(pkt->data)
                        pkt->duration = AV_RL16(pkt->data) / wsvqa->channels;
238 239 240 241 242 243 244 245
                    break;
                case SND2_TAG:
                    /* 2 samples/byte, 1 or 2 samples per frame depending on stereo */
                    pkt->duration = (chunk_size * 2) / wsvqa->channels;
                    break;
                }
                break;
            case VQFR_TAG:
Kostya Shishkov's avatar
Kostya Shishkov committed
246
                pkt->stream_index = wsvqa->video_stream_index;
247
                pkt->duration = 1;
248
                break;
Kostya Shishkov's avatar
Kostya Shishkov committed
249
            }
250

Kostya Shishkov's avatar
Kostya Shishkov committed
251 252
            /* stay on 16-bit alignment */
            if (skip_byte)
253
                avio_skip(pb, 1);
Kostya Shishkov's avatar
Kostya Shishkov committed
254 255

            return ret;
256
        } else {
Kostya Shishkov's avatar
Kostya Shishkov committed
257 258 259 260 261 262
            switch(chunk_type){
            case CMDS_TAG:
                break;
            default:
                av_log(s, AV_LOG_INFO, "Skipping unknown chunk 0x%08X\n", chunk_type);
            }
263
            avio_skip(pb, chunk_size + skip_byte);
264
        }
Kostya Shishkov's avatar
Kostya Shishkov committed
265
    }
266 267 268 269

    return ret;
}

270
AVInputFormat ff_wsvqa_demuxer = {
271
    .name           = "wsvqa",
272
    .long_name      = NULL_IF_CONFIG_SMALL("Westwood Studios VQA"),
273 274 275 276
    .priv_data_size = sizeof(WsVqaDemuxContext),
    .read_probe     = wsvqa_probe,
    .read_header    = wsvqa_read_header,
    .read_packet    = wsvqa_read_packet,
277
};