smush.c 7.28 KB
Newer Older
Paul B Mahol's avatar
Paul B Mahol committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * LucasArts Smush demuxer
 * Copyright (c) 2006 Cyril Zorin
 *
 * 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
 */

#include "libavutil/intreadwrite.h"
Paul B Mahol's avatar
Paul B Mahol committed
23

Paul B Mahol's avatar
Paul B Mahol committed
24 25
#include "avformat.h"
#include "avio.h"
Paul B Mahol's avatar
Paul B Mahol committed
26
#include "internal.h"
Paul B Mahol's avatar
Paul B Mahol committed
27

Paul B Mahol's avatar
Paul B Mahol committed
28
typedef struct SMUSHContext {
Paul B Mahol's avatar
Paul B Mahol committed
29 30 31 32 33 34 35
    int version;
    int audio_stream_index;
    int video_stream_index;
} SMUSHContext;

static int smush_read_probe(AVProbeData *p)
{
Paul B Mahol's avatar
Paul B Mahol committed
36
    if (((AV_RL32(p->buf)     == MKTAG('S', 'A', 'N', 'M') &&
Paul B Mahol's avatar
Paul B Mahol committed
37
          AV_RL32(p->buf + 8) == MKTAG('S', 'H', 'D', 'R')) ||
Paul B Mahol's avatar
Paul B Mahol committed
38
         (AV_RL32(p->buf)     == MKTAG('A', 'N', 'I', 'M') &&
Paul B Mahol's avatar
Paul B Mahol committed
39
          AV_RL32(p->buf + 8) == MKTAG('A', 'H', 'D', 'R')))) {
Paul B Mahol's avatar
Paul B Mahol committed
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
        return AVPROBE_SCORE_MAX;
    }

    return 0;
}

static int smush_read_header(AVFormatContext *ctx)
{
    SMUSHContext *smush = ctx->priv_data;
    AVIOContext *pb = ctx->pb;
    AVStream *vst, *ast;
    uint32_t magic, nframes, size, subversion, i;
    uint32_t width = 0, height = 0, got_audio = 0, read = 0;
    uint32_t sample_rate, channels, palette[256];

    magic = avio_rb32(pb);
    avio_skip(pb, 4); // skip movie size

    if (magic == MKBETAG('A', 'N', 'I', 'M')) {
        if (avio_rb32(pb) != MKBETAG('A', 'H', 'D', 'R'))
            return AVERROR_INVALIDDATA;

        size = avio_rb32(pb);
        if (size < 3 * 256 + 6)
            return AVERROR_INVALIDDATA;

        smush->version = 0;
        subversion     = avio_rl16(pb);
        nframes        = avio_rl16(pb);
Paul B Mahol's avatar
Paul B Mahol committed
69 70
        if (!nframes)
            return AVERROR_INVALIDDATA;
Paul B Mahol's avatar
Paul B Mahol committed
71 72 73 74 75 76 77

        avio_skip(pb, 2); // skip pad

        for (i = 0; i < 256; i++)
            palette[i] = avio_rb24(pb);

        avio_skip(pb, size - (3 * 256 + 6));
Paul B Mahol's avatar
Paul B Mahol committed
78
    } else if (magic == MKBETAG('S', 'A', 'N', 'M')) {
Paul B Mahol's avatar
Paul B Mahol committed
79 80 81 82 83 84 85 86
        if (avio_rb32(pb) != MKBETAG('S', 'H', 'D', 'R'))
            return AVERROR_INVALIDDATA;

        size = avio_rb32(pb);
        if (size < 14)
            return AVERROR_INVALIDDATA;

        smush->version = 1;
Paul B Mahol's avatar
Paul B Mahol committed
87
        subversion = avio_rl16(pb);
Paul B Mahol's avatar
Paul B Mahol committed
88
        nframes = avio_rl32(pb);
Paul B Mahol's avatar
Paul B Mahol committed
89 90 91
        if (!nframes)
            return AVERROR_INVALIDDATA;

Paul B Mahol's avatar
Paul B Mahol committed
92 93 94 95 96 97 98 99 100 101 102 103 104
        avio_skip(pb, 2); // skip pad
        width  = avio_rl16(pb);
        height = avio_rl16(pb);
        avio_skip(pb, 2); // skip pad
        avio_skip(pb, size - 14);

        if (avio_rb32(pb) != MKBETAG('F', 'L', 'H', 'D'))
            return AVERROR_INVALIDDATA;

        size = avio_rb32(pb);
        while (!got_audio && ((read + 8) < size)) {
            uint32_t sig, chunk_size;

105
            if (avio_feof(pb))
Paul B Mahol's avatar
Paul B Mahol committed
106 107 108 109
                return AVERROR_EOF;

            sig        = avio_rb32(pb);
            chunk_size = avio_rb32(pb);
Paul B Mahol's avatar
Paul B Mahol committed
110
            read      += 8;
Paul B Mahol's avatar
Paul B Mahol committed
111 112 113 114
            switch (sig) {
            case MKBETAG('W', 'a', 'v', 'e'):
                got_audio = 1;
                sample_rate = avio_rl32(pb);
Paul B Mahol's avatar
Paul B Mahol committed
115 116 117 118 119 120 121
                if (!sample_rate)
                    return AVERROR_INVALIDDATA;

                channels = avio_rl32(pb);
                if (!channels)
                    return AVERROR_INVALIDDATA;

Paul B Mahol's avatar
Paul B Mahol committed
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
                avio_skip(pb, chunk_size - 8);
                read += chunk_size;
                break;
            case MKBETAG('B', 'l', '1', '6'):
            case MKBETAG('A', 'N', 'N', 'O'):
                avio_skip(pb, chunk_size);
                read += chunk_size;
                break;
            default:
                return AVERROR_INVALIDDATA;
                break;
            }
        }

        avio_skip(pb, size - read);
    } else {
        av_log(ctx, AV_LOG_ERROR, "Wrong magic\n");
        return AVERROR_INVALIDDATA;
    }

    vst = avformat_new_stream(ctx, 0);
    if (!vst)
        return AVERROR(ENOMEM);

    smush->video_stream_index = vst->index;

Paul B Mahol's avatar
Paul B Mahol committed
148 149
    avpriv_set_pts_info(vst, 64, 1, 15);

Paul B Mahol's avatar
Paul B Mahol committed
150 151 152
    vst->start_time        = 0;
    vst->duration          =
    vst->nb_frames         = nframes;
Paul B Mahol's avatar
Paul B Mahol committed
153
    vst->avg_frame_rate    = av_inv_q(vst->time_base);
154 155 156 157 158
    vst->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    vst->codecpar->codec_id   = AV_CODEC_ID_SANM;
    vst->codecpar->codec_tag  = 0;
    vst->codecpar->width      = width;
    vst->codecpar->height     = height;
Paul B Mahol's avatar
Paul B Mahol committed
159 160

    if (!smush->version) {
161
        if (ff_alloc_extradata(vst->codecpar, 1024 + 2))
Paul B Mahol's avatar
Paul B Mahol committed
162 163
            return AVERROR(ENOMEM);

164
        AV_WL16(vst->codecpar->extradata, subversion);
Paul B Mahol's avatar
Paul B Mahol committed
165
        for (i = 0; i < 256; i++)
166
            AV_WL32(vst->codecpar->extradata + 2 + i * 4, palette[i]);
Paul B Mahol's avatar
Paul B Mahol committed
167 168 169 170 171 172 173 174 175 176
    }

    if (got_audio) {
        ast = avformat_new_stream(ctx, 0);
        if (!ast)
            return AVERROR(ENOMEM);

        smush->audio_stream_index = ast->index;

        ast->start_time         = 0;
177 178 179 180 181
        ast->codecpar->codec_type  = AVMEDIA_TYPE_AUDIO;
        ast->codecpar->codec_id    = AV_CODEC_ID_ADPCM_VIMA;
        ast->codecpar->codec_tag   = 0;
        ast->codecpar->sample_rate = sample_rate;
        ast->codecpar->channels    = channels;
Paul B Mahol's avatar
Paul B Mahol committed
182

183
        avpriv_set_pts_info(ast, 64, 1, ast->codecpar->sample_rate);
Paul B Mahol's avatar
Paul B Mahol committed
184 185 186 187 188 189 190 191 192 193
    }

    return 0;
}

static int smush_read_packet(AVFormatContext *ctx, AVPacket *pkt)
{
    SMUSHContext *smush = ctx->priv_data;
    AVIOContext *pb = ctx->pb;
    int done = 0;
Paul B Mahol's avatar
Paul B Mahol committed
194
    int ret;
Paul B Mahol's avatar
Paul B Mahol committed
195 196 197 198

    while (!done) {
        uint32_t sig, size;

199
        if (avio_feof(pb))
Paul B Mahol's avatar
Paul B Mahol committed
200 201
            return AVERROR_EOF;

Paul B Mahol's avatar
Paul B Mahol committed
202 203
        sig  = avio_rb32(pb);
        size = avio_rb32(pb);
Paul B Mahol's avatar
Paul B Mahol committed
204 205 206 207 208

        switch (sig) {
        case MKBETAG('F', 'R', 'M', 'E'):
            if (smush->version)
                break;
Paul B Mahol's avatar
Paul B Mahol committed
209 210
            if ((ret = av_get_packet(pb, pkt, size)) < 0)
                return ret;
Paul B Mahol's avatar
Paul B Mahol committed
211 212 213 214 215

            pkt->stream_index = smush->video_stream_index;
            done = 1;
            break;
        case MKBETAG('B', 'l', '1', '6'):
Paul B Mahol's avatar
Paul B Mahol committed
216 217
            if ((ret = av_get_packet(pb, pkt, size)) < 0)
                return ret;
Paul B Mahol's avatar
Paul B Mahol committed
218 219 220 221 222 223

            pkt->stream_index = smush->video_stream_index;
            pkt->duration = 1;
            done = 1;
            break;
        case MKBETAG('W', 'a', 'v', 'e'):
224 225
            if (size < 13)
                return AVERROR_INVALIDDATA;
226
            if (av_get_packet(pb, pkt, size) < 13)
Paul B Mahol's avatar
Paul B Mahol committed
227 228 229
                return AVERROR(EIO);

            pkt->stream_index = smush->audio_stream_index;
230
            pkt->flags       |= AV_PKT_FLAG_KEY;
Paul B Mahol's avatar
Paul B Mahol committed
231
            pkt->duration     = AV_RB32(pkt->data);
232 233
            if (pkt->duration == 0xFFFFFFFFu)
                pkt->duration = AV_RB32(pkt->data + 8);
Paul B Mahol's avatar
Paul B Mahol committed
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
            done = 1;
            break;
        default:
            avio_skip(pb, size);
            break;
        }
    }

    return 0;
}

AVInputFormat ff_smush_demuxer = {
    .name           = "smush",
    .long_name      = NULL_IF_CONFIG_SMALL("LucasArts Smush"),
    .priv_data_size = sizeof(SMUSHContext),
    .read_probe     = smush_read_probe,
    .read_header    = smush_read_header,
    .read_packet    = smush_read_packet,
};