westwood_vqa.c 8.77 KB
Newer Older
1
/*
2
 * Westwood Studios VQA Format Demuxer
3
 * Copyright (c) 2003 The FFmpeg project
4
 *
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
    AVStream *st;
84 85 86 87
    uint8_t *header;
    uint8_t scratch[VQA_PREAMBLE_SIZE];
    uint32_t chunk_tag;
    uint32_t 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 97 98
    st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    st->codecpar->codec_id = AV_CODEC_ID_WS_VQA;
    st->codecpar->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
    if (ff_get_extradata(s, st->codecpar, pb, VQA_HEADER_SIZE) < 0)
105
        return AVERROR(ENOMEM);
106
    header = st->codecpar->extradata;
107 108
    st->codecpar->width = AV_RL16(&header[6]);
    st->codecpar->height = AV_RL16(&header[8]);
109
    fps = header[12];
110 111
    st->nb_frames =
    st->duration  = AV_RL16(&header[4]);
112 113 114 115 116
    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);
117

118 119 120 121 122
    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;
123

124
    s->ctx_flags |= AVFMTCTX_NOHEADER;
125

126 127 128
    /* 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 {
129
        if (avio_read(pb, scratch, VQA_PREAMBLE_SIZE) != VQA_PREAMBLE_SIZE)
130
            return AVERROR(EIO);
131 132
        chunk_tag = AV_RB32(&scratch[0]);
        chunk_size = AV_RB32(&scratch[4]);
133

134
        /* catch any unknown header tags, for curiosity */
135 136 137 138 139 140 141 142
        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
143
        case CMDS_TAG:
144 145 146
            break;

        default:
147 148
            av_log(s, AV_LOG_ERROR, " note: unknown chunk seen (%s)\n",
                   av_fourcc2str(chunk_tag));
149 150 151
            break;
        }

152
        avio_skip(pb, chunk_size);
153
    } while (chunk_tag != FINF_TAG);
154 155 156 157 158 159 160

    return 0;
}

static int wsvqa_read_packet(AVFormatContext *s,
                             AVPacket *pkt)
{
161
    WsVqaDemuxContext *wsvqa = s->priv_data;
162
    AVIOContext *pb = s->pb;
Kostya Shishkov's avatar
Kostya Shishkov committed
163
    int ret = -1;
164 165 166
    uint8_t preamble[VQA_PREAMBLE_SIZE];
    uint32_t chunk_type;
    uint32_t chunk_size;
167 168
    int skip_byte;

169
    while (avio_read(pb, preamble, VQA_PREAMBLE_SIZE) == VQA_PREAMBLE_SIZE) {
170 171
        chunk_type = AV_RB32(&preamble[0]);
        chunk_size = AV_RB32(&preamble[4]);
172

Kostya Shishkov's avatar
Kostya Shishkov committed
173 174
        skip_byte = chunk_size & 0x01;

175 176
        if ((chunk_type == SND0_TAG) || (chunk_type == SND1_TAG) ||
            (chunk_type == SND2_TAG) || (chunk_type == VQFR_TAG)) {
Kostya Shishkov's avatar
Kostya Shishkov committed
177

178 179
            ret= av_get_packet(pb, pkt, chunk_size);
            if (ret<0)
180
                return AVERROR(EIO);
Kostya Shishkov's avatar
Kostya Shishkov committed
181

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
            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;
198 199 200 201
                    st->codecpar->sample_rate = wsvqa->sample_rate;
                    st->codecpar->bits_per_coded_sample = wsvqa->bps;
                    st->codecpar->channels = wsvqa->channels;
                    st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
202

203
                    avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);
204 205 206 207

                    switch (chunk_type) {
                    case SND0_TAG:
                        if (wsvqa->bps == 16)
208
                            st->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE;
209
                        else
210
                            st->codecpar->codec_id = AV_CODEC_ID_PCM_U8;
211 212
                        break;
                    case SND1_TAG:
213
                        st->codecpar->codec_id = AV_CODEC_ID_WESTWOOD_SND1;
214 215
                        break;
                    case SND2_TAG:
216
                        st->codecpar->codec_id = AV_CODEC_ID_ADPCM_IMA_WS;
217
                        if (ff_alloc_extradata(st->codecpar, 2))
218
                            return AVERROR(ENOMEM);
219
                        AV_WL16(st->codecpar->extradata, wsvqa->version);
220 221 222 223
                        break;
                    }
                }

Kostya Shishkov's avatar
Kostya Shishkov committed
224
                pkt->stream_index = wsvqa->audio_stream_index;
225 226 227
                switch (chunk_type) {
                case SND1_TAG:
                    /* unpacked size is stored in header */
228 229
                    if(pkt->data)
                        pkt->duration = AV_RL16(pkt->data) / wsvqa->channels;
230 231 232 233 234 235 236 237
                    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
238
                pkt->stream_index = wsvqa->video_stream_index;
239
                pkt->duration = 1;
240
                break;
Kostya Shishkov's avatar
Kostya Shishkov committed
241
            }
242

Kostya Shishkov's avatar
Kostya Shishkov committed
243 244
            /* stay on 16-bit alignment */
            if (skip_byte)
245
                avio_skip(pb, 1);
Kostya Shishkov's avatar
Kostya Shishkov committed
246 247

            return ret;
248
        } else {
Kostya Shishkov's avatar
Kostya Shishkov committed
249 250 251 252
            switch(chunk_type){
            case CMDS_TAG:
                break;
            default:
253 254
                av_log(s, AV_LOG_INFO, "Skipping unknown chunk %s\n",
                       av_fourcc2str(av_bswap32(chunk_type)));
Kostya Shishkov's avatar
Kostya Shishkov committed
255
            }
256
            avio_skip(pb, chunk_size + skip_byte);
257
        }
Kostya Shishkov's avatar
Kostya Shishkov committed
258
    }
259 260 261 262

    return ret;
}

263
AVInputFormat ff_wsvqa_demuxer = {
264
    .name           = "wsvqa",
265
    .long_name      = NULL_IF_CONFIG_SMALL("Westwood Studios VQA"),
266 267 268 269
    .priv_data_size = sizeof(WsVqaDemuxContext),
    .read_probe     = wsvqa_probe,
    .read_header    = wsvqa_read_header,
    .read_packet    = wsvqa_read_packet,
270
};