4xm.c 12.1 KB
Newer Older
1 2 3 4
/*
 * 4X Technologies .4xm File Demuxer (no muxer)
 * 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 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
    float 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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
        return AVERROR_INVALIDDATA;
    }

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

    avpriv_set_pts_info(st, 60, 1, fourxm->fps);

    fourxm->video_stream_index = st->index;

    st->codec->codec_type     = AVMEDIA_TYPE_VIDEO;
    st->codec->codec_id       = AV_CODEC_ID_4XM;
    st->codec->extradata_size = 4;
    st->codec->extradata      = av_malloc(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,
124 125
                      FourxmDemuxContext *fourxm, uint8_t *buf, int size,
                      int left)
126 127 128 129
{
    AVStream *st;
    int track;
    /* check that there is enough data */
130
    if (size != strk_SIZE || left < size + 8)
131 132 133
        return AVERROR_INVALIDDATA;

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

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    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 ||
154
        fourxm->tracks[track].bits        <= 0) {
155 156 157
        av_log(s, AV_LOG_ERROR, "audio header invalid\n");
        return AVERROR_INVALIDDATA;
    }
158 159 160 161 162
    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;
    }

163 164 165 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
    /* 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;
}

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

    fourxm->track_count = 0;
205 206
    fourxm->tracks      = NULL;
    fourxm->fps         = 1.0;
207 208

    /* skip the first 3 32-bit numbers */
209
    avio_skip(pb, 12);
210 211 212 213

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

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

    /* take the lazy approach and search for any and all vtrk and strk chunks */
    for (i = 0; i < header_size - 8; i++) {
228
        fourcc_tag = AV_RL32(&header[i]);
229
        size       = AV_RL32(&header[i + 4]);
230 231 232 233
        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;
        }
234

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

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

253
            i += 8 + size;
254 255 256 257 258
        }
    }

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

264
    av_free(header);
265
    /* initialize context members */
266
    fourxm->video_pts = -1;  /* first frame will push to 0 */
267

268
    return 0;
269 270 271 272
fail:
    av_freep(&fourxm->tracks);
    av_free(header);
    return ret;
273 274 275
}

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

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

300
            /* skip the LIST-* tag and move on to the next fourcc */
301
            avio_rl32(pb);
302 303
            break;

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

320
            if (ret < 0) {
321
                av_free_packet(pkt);
322
            } else
323
                packet_read = 1;
324
            break;
325

326
        case snd__TAG:
327
            track_number = avio_rl32(pb);
Mans Rullgard's avatar
Mans Rullgard committed
328
            avio_skip(pb, 4);
329
            size -= 8;
Michael Niedermayer's avatar
Michael Niedermayer committed
330

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

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

        default:
358
            avio_skip(pb, size);
359 360 361 362 363 364 365 366
            break;
        }
    }
    return ret;
}

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

369
    av_freep(&fourxm->tracks);
370 371 372 373

    return 0;
}

374
AVInputFormat ff_fourxm_demuxer = {
375
    .name           = "4xm",
376
    .long_name      = NULL_IF_CONFIG_SMALL("4X Technologies"),
377 378 379 380 381
    .priv_data_size = sizeof(FourxmDemuxContext),
    .read_probe     = fourxm_probe,
    .read_header    = fourxm_read_header,
    .read_packet    = fourxm_read_packet,
    .read_close     = fourxm_read_close,
382
};