4xm.c 12.3 KB
Newer Older
1 2
/*
 * 4X Technologies .4xm File Demuxer (no muxer)
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 25 26 27 28 29
 * 4X Technologies file demuxer
 * by Mike Melanson (melanson@pcisys.net)
 * for more information on the .4xm file format, visit:
 *   http://www.pcisys.net/~melanson/codecs/
 */

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

Diego Biurrun's avatar
Diego Biurrun committed
35
#define     RIFF_TAG MKTAG('R', 'I', 'F', 'F')
36
#define  FOURXMV_TAG MKTAG('4', 'X', 'M', 'V')
Diego Biurrun's avatar
Diego Biurrun committed
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
#define     LIST_TAG MKTAG('L', 'I', 'S', 'T')
#define     HEAD_TAG MKTAG('H', 'E', 'A', 'D')
#define     TRK__TAG MKTAG('T', 'R', 'K', '_')
#define     MOVI_TAG MKTAG('M', 'O', 'V', 'I')
#define     VTRK_TAG MKTAG('V', 'T', 'R', 'K')
#define     STRK_TAG MKTAG('S', 'T', 'R', 'K')
#define     std__TAG MKTAG('s', 't', 'd', '_')
#define     name_TAG MKTAG('n', 'a', 'm', 'e')
#define     vtrk_TAG MKTAG('v', 't', 'r', 'k')
#define     strk_TAG MKTAG('s', 't', 'r', 'k')
#define     ifrm_TAG MKTAG('i', 'f', 'r', 'm')
#define     pfrm_TAG MKTAG('p', 'f', 'r', 'm')
#define     cfrm_TAG MKTAG('c', 'f', 'r', 'm')
#define     ifr2_TAG MKTAG('i', 'f', 'r', '2')
#define     pfr2_TAG MKTAG('p', 'f', 'r', '2')
#define     cfr2_TAG MKTAG('c', 'f', 'r', '2')
#define     snd__TAG MKTAG('s', 'n', 'd', '_')
54 55 56 57 58

#define vtrk_SIZE 0x44
#define strk_SIZE 0x28

#define GET_LIST_HEADER() \
59
    fourcc_tag = avio_rl32(pb); \
60
    size       = avio_rl32(pb); \
61 62
    if (fourcc_tag != LIST_TAG) \
        return AVERROR_INVALIDDATA; \
63
    fourcc_tag = avio_rl32(pb);
64 65 66 67 68

typedef struct AudioTrack {
    int sample_rate;
    int bits;
    int channels;
69
    int stream_index;
Michael Niedermayer's avatar
Michael Niedermayer committed
70
    int adpcm;
71
    int64_t audio_pts;
72 73 74
} AudioTrack;

typedef struct FourxmDemuxContext {
75
    int video_stream_index;
76 77
    int track_count;
    AudioTrack *tracks;
78

79
    int64_t video_pts;
80
    AVRational fps;
81 82 83 84
} FourxmDemuxContext;

static int fourxm_probe(AVProbeData *p)
{
85
    if ((AV_RL32(&p->buf[0]) != RIFF_TAG) ||
86
        (AV_RL32(&p->buf[8]) != FOURXMV_TAG))
87 88 89 90 91
        return 0;

    return AVPROBE_SCORE_MAX;
}

92
static int parse_vtrk(AVFormatContext *s,
93 94
                      FourxmDemuxContext *fourxm, uint8_t *buf, int size,
                      int left)
95 96 97
{
    AVStream *st;
    /* check that there is enough data */
98
    if (size != vtrk_SIZE || left < size + 8) {
99 100 101 102 103 104 105 106
        return AVERROR_INVALIDDATA;
    }

    /* allocate a new AVStream */
    st = avformat_new_stream(s, NULL);
    if (!st)
        return AVERROR(ENOMEM);

107
    avpriv_set_pts_info(st, 60, fourxm->fps.den, fourxm->fps.num);
108 109 110 111 112

    fourxm->video_stream_index = st->index;

    st->codec->codec_type     = AVMEDIA_TYPE_VIDEO;
    st->codec->codec_id       = AV_CODEC_ID_4XM;
113 114 115 116

    st->codec->extradata      = av_mallocz(4 + FF_INPUT_BUFFER_PADDING_SIZE);
    if (!st->codec->extradata)
        return AVERROR(ENOMEM);
117 118 119 120 121 122 123 124 125 126
    st->codec->extradata_size = 4;
    AV_WL32(st->codec->extradata, AV_RL32(buf + 16));
    st->codec->width  = AV_RL32(buf + 36);
    st->codec->height = AV_RL32(buf + 40);

    return 0;
}


static int parse_strk(AVFormatContext *s,
127 128
                      FourxmDemuxContext *fourxm, uint8_t *buf, int size,
                      int left)
129 130 131 132
{
    AVStream *st;
    int track;
    /* check that there is enough data */
133
    if (size != strk_SIZE || left < size + 8)
134 135 136
        return AVERROR_INVALIDDATA;

    track = AV_RL32(buf + 8);
137 138 139 140
    if ((unsigned)track >= UINT_MAX / sizeof(AudioTrack) - 1) {
        av_log(s, AV_LOG_ERROR, "current_track too large\n");
        return AVERROR_INVALIDDATA;
    }
141

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
    if (track + 1 > fourxm->track_count) {
        if (av_reallocp_array(&fourxm->tracks, track + 1, sizeof(AudioTrack)))
            return AVERROR(ENOMEM);
        memset(&fourxm->tracks[fourxm->track_count], 0,
               sizeof(AudioTrack) * (track + 1 - fourxm->track_count));
        fourxm->track_count = track + 1;
    }
    fourxm->tracks[track].adpcm       = AV_RL32(buf + 12);
    fourxm->tracks[track].channels    = AV_RL32(buf + 36);
    fourxm->tracks[track].sample_rate = AV_RL32(buf + 40);
    fourxm->tracks[track].bits        = AV_RL32(buf + 44);
    fourxm->tracks[track].audio_pts   = 0;

    if (fourxm->tracks[track].channels    <= 0 ||
        fourxm->tracks[track].sample_rate <= 0 ||
157
        fourxm->tracks[track].bits        <= 0) {
158 159 160
        av_log(s, AV_LOG_ERROR, "audio header invalid\n");
        return AVERROR_INVALIDDATA;
    }
161 162 163 164 165
    if (!fourxm->tracks[track].adpcm && fourxm->tracks[track].bits<8) {
        av_log(s, AV_LOG_ERROR, "bits unspecified for non ADPCM\n");
        return AVERROR_INVALIDDATA;
    }

166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
    /* allocate a new AVStream */
    st = avformat_new_stream(s, NULL);
    if (!st)
        return AVERROR(ENOMEM);

    st->id = track;
    avpriv_set_pts_info(st, 60, 1, fourxm->tracks[track].sample_rate);

    fourxm->tracks[track].stream_index = st->index;

    st->codec->codec_type            = AVMEDIA_TYPE_AUDIO;
    st->codec->codec_tag             = 0;
    st->codec->channels              = fourxm->tracks[track].channels;
    st->codec->sample_rate           = fourxm->tracks[track].sample_rate;
    st->codec->bits_per_coded_sample = fourxm->tracks[track].bits;
    st->codec->bit_rate              = st->codec->channels *
                                       st->codec->sample_rate *
                                       st->codec->bits_per_coded_sample;
    st->codec->block_align           = st->codec->channels *
                                       st->codec->bits_per_coded_sample;

    if (fourxm->tracks[track].adpcm){
        st->codec->codec_id = AV_CODEC_ID_ADPCM_4XM;
    } else if (st->codec->bits_per_coded_sample == 8) {
        st->codec->codec_id = AV_CODEC_ID_PCM_U8;
    } else
        st->codec->codec_id = AV_CODEC_ID_PCM_S16LE;

    return 0;
}

197
static int fourxm_read_header(AVFormatContext *s)
198
{
199
    AVIOContext *pb = s->pb;
200 201 202
    unsigned int fourcc_tag;
    unsigned int size;
    int header_size;
203
    FourxmDemuxContext *fourxm = s->priv_data;
204
    unsigned char *header;
205
    int i, ret;
206 207

    fourxm->track_count = 0;
208
    fourxm->tracks      = NULL;
209
    fourxm->fps         = (AVRational){1,1};
210 211

    /* skip the first 3 32-bit numbers */
212
    avio_skip(pb, 12);
213 214 215 216

    /* check for LIST-HEAD */
    GET_LIST_HEADER();
    header_size = size - 4;
217
    if (fourcc_tag != HEAD_TAG || header_size < 0)
218 219 220 221 222
        return AVERROR_INVALIDDATA;

    /* allocate space for the header and load the whole thing */
    header = av_malloc(header_size);
    if (!header)
223
        return AVERROR(ENOMEM);
224
    if (avio_read(pb, header, header_size) != header_size) {
225
        av_free(header);
226
        return AVERROR(EIO);
227
    }
228 229 230

    /* take the lazy approach and search for any and all vtrk and strk chunks */
    for (i = 0; i < header_size - 8; i++) {
231
        fourcc_tag = AV_RL32(&header[i]);
232
        size       = AV_RL32(&header[i + 4]);
233 234 235 236
        if (size > header_size - i - 8 && (fourcc_tag == vtrk_TAG || fourcc_tag == strk_TAG)) {
            av_log(s, AV_LOG_ERROR, "chunk larger than array %d>%d\n", size, header_size - i - 8);
            return AVERROR_INVALIDDATA;
        }
237

238
        if (fourcc_tag == std__TAG) {
239
            if (header_size - i < 16) {
240
                av_log(s, AV_LOG_ERROR, "std TAG truncated\n");
241 242
                ret = AVERROR_INVALIDDATA;
                goto fail;
243
            }
244
            fourxm->fps = av_d2q(av_int2float(AV_RL32(&header[i + 12])), 10000);
245
        } else if (fourcc_tag == vtrk_TAG) {
246 247
            if ((ret = parse_vtrk(s, fourxm, header + i, size,
                                  header_size - i)) < 0)
248
                goto fail;
249

250
            i += 8 + size;
251
        } else if (fourcc_tag == strk_TAG) {
252 253
            if ((ret = parse_strk(s, fourxm, header + i, size,
                                  header_size - i)) < 0)
254
                goto fail;
255

256
            i += 8 + size;
257 258 259 260 261
        }
    }

    /* skip over the LIST-MOVI chunk (which is where the stream should be */
    GET_LIST_HEADER();
262 263
    if (fourcc_tag != MOVI_TAG) {
        ret = AVERROR_INVALIDDATA;
264 265
        goto fail;
    }
266

267
    av_free(header);
268
    /* initialize context members */
269
    fourxm->video_pts = -1;  /* first frame will push to 0 */
270

271
    return 0;
272 273 274 275
fail:
    av_freep(&fourxm->tracks);
    av_free(header);
    return ret;
276 277 278
}

static int fourxm_read_packet(AVFormatContext *s,
279
                              AVPacket *pkt)
280 281
{
    FourxmDemuxContext *fourxm = s->priv_data;
282
    AVIOContext *pb            = s->pb;
283
    unsigned int fourcc_tag;
Mans Rullgard's avatar
Mans Rullgard committed
284
    unsigned int size;
285
    int ret = 0;
286
    unsigned int track_number;
287
    int packet_read = 0;
288
    unsigned char header[8];
289
    int audio_frame_count;
290 291

    while (!packet_read) {
292
        if ((ret = avio_read(s->pb, header, 8)) < 0)
293
            return ret;
294
        fourcc_tag = AV_RL32(&header[0]);
295
        size       = AV_RL32(&header[4]);
296
        if (avio_feof(pb))
297
            return AVERROR(EIO);
298
        switch (fourcc_tag) {
299
        case LIST_TAG:
300
            /* this is a good time to bump the video pts */
301
            fourxm->video_pts++;
302

303
            /* skip the LIST-* tag and move on to the next fourcc */
304
            avio_rl32(pb);
305 306
            break;

307 308
        case ifrm_TAG:
        case pfrm_TAG:
309 310 311 312
        case cfrm_TAG:
        case ifr2_TAG:
        case pfr2_TAG:
        case cfr2_TAG:
313 314
            /* allocate 8 more bytes than 'size' to account for fourcc
             * and size */
315
            if (size + 8 < size || av_new_packet(pkt, size + 8))
316
                return AVERROR(EIO);
317
            pkt->stream_index = fourxm->video_stream_index;
318 319
            pkt->pts          = fourxm->video_pts;
            pkt->pos          = avio_tell(s->pb);
320
            memcpy(pkt->data, header, 8);
321
            ret = avio_read(s->pb, &pkt->data[8], size);
322

323
            if (ret < 0) {
324
                av_free_packet(pkt);
325
            } else {
326
                packet_read = 1;
327 328
                av_shrink_packet(pkt, ret + 8);
            }
329
            break;
330

331
        case snd__TAG:
332
            track_number = avio_rl32(pb);
Mans Rullgard's avatar
Mans Rullgard committed
333
            avio_skip(pb, 4);
334
            size -= 8;
Michael Niedermayer's avatar
Michael Niedermayer committed
335

336 337 338 339
            if (track_number < fourxm->track_count &&
                fourxm->tracks[track_number].channels > 0) {
                ret = av_get_packet(s->pb, pkt, size);
                if (ret < 0)
340
                    return AVERROR(EIO);
341
                pkt->stream_index =
342
                    fourxm->tracks[track_number].stream_index;
343
                pkt->pts    = fourxm->tracks[track_number].audio_pts;
Michael Niedermayer's avatar
Michael Niedermayer committed
344
                packet_read = 1;
345

346 347
                /* pts accounting */
                audio_frame_count = size;
348
                if (fourxm->tracks[track_number].adpcm)
349 350 351
                    audio_frame_count -= 2 * (fourxm->tracks[track_number].channels);
                audio_frame_count /= fourxm->tracks[track_number].channels;
                if (fourxm->tracks[track_number].adpcm) {
352
                    audio_frame_count *= 2;
353
                } else
354
                    audio_frame_count /=
355
                        (fourxm->tracks[track_number].bits / 8);
356
                fourxm->tracks[track_number].audio_pts += audio_frame_count;
357
            } else {
358
                avio_skip(pb, size);
359 360 361 362
            }
            break;

        default:
363
            avio_skip(pb, size);
364 365 366 367 368 369 370 371
            break;
        }
    }
    return ret;
}

static int fourxm_read_close(AVFormatContext *s)
{
372
    FourxmDemuxContext *fourxm = s->priv_data;
373

374
    av_freep(&fourxm->tracks);
375 376 377 378

    return 0;
}

379
AVInputFormat ff_fourxm_demuxer = {
380
    .name           = "4xm",
381
    .long_name      = NULL_IF_CONFIG_SMALL("4X Technologies"),
382 383 384 385 386
    .priv_data_size = sizeof(FourxmDemuxContext),
    .read_probe     = fourxm_probe,
    .read_header    = fourxm_read_header,
    .read_packet    = fourxm_read_packet,
    .read_close     = fourxm_read_close,
387
};