rpl.c 12.5 KB
Newer Older
Eli Friedman's avatar
Eli Friedman committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/*
 * ARMovie/RPL demuxer
 * Copyright (c) 2007 Christian Ohm, 2008 Eli Friedman
 *
 * 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
 */

22
#include <inttypes.h>
23 24
#include <stdlib.h>

25
#include "libavutil/avstring.h"
26
#include "libavutil/dict.h"
Eli Friedman's avatar
Eli Friedman committed
27
#include "avformat.h"
28
#include "internal.h"
Eli Friedman's avatar
Eli Friedman committed
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

#define RPL_SIGNATURE "ARMovie\x0A"
#define RPL_SIGNATURE_SIZE 8

/** 256 is arbitrary, but should be big enough for any reasonable file. */
#define RPL_LINE_LENGTH 256

static int rpl_probe(AVProbeData *p)
{
    if (memcmp(p->buf, RPL_SIGNATURE, RPL_SIGNATURE_SIZE))
        return 0;

    return AVPROBE_SCORE_MAX;
}

typedef struct RPLContext {
    // RPL header data
    int32_t frames_per_chunk;

    // Stream position data
    uint32_t chunk_number;
    uint32_t chunk_part;
    uint32_t frame_in_part;
} RPLContext;

54
static int read_line(AVIOContext * pb, char* line, int bufsize)
Eli Friedman's avatar
Eli Friedman committed
55 56 57
{
    int i;
    for (i = 0; i < bufsize - 1; i++) {
58
        int b = avio_r8(pb);
Eli Friedman's avatar
Eli Friedman committed
59 60 61 62
        if (b == 0)
            break;
        if (b == '\n') {
            line[i] = '\0';
63
            return avio_feof(pb) ? -1 : 0;
Eli Friedman's avatar
Eli Friedman committed
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
        }
        line[i] = b;
    }
    line[i] = '\0';
    return -1;
}

static int32_t read_int(const char* line, const char** endptr, int* error)
{
    unsigned long result = 0;
    for (; *line>='0' && *line<='9'; line++) {
        if (result > (0x7FFFFFFF - 9) / 10)
            *error = -1;
        result = 10 * result + *line - '0';
    }
    *endptr = line;
    return result;
}

83
static int32_t read_line_and_int(AVIOContext * pb, int* error)
Eli Friedman's avatar
Eli Friedman committed
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
{
    char line[RPL_LINE_LENGTH];
    const char *endptr;
    *error |= read_line(pb, line, sizeof(line));
    return read_int(line, &endptr, error);
}

/** Parsing for fps, which can be a fraction. Unfortunately,
  * the spec for the header leaves out a lot of details,
  * so this is mostly guessing.
  */
static AVRational read_fps(const char* line, int* error)
{
    int64_t num, den = 1;
    AVRational result;
    num = read_int(line, &line, error);
    if (*line == '.')
        line++;
    for (; *line>='0' && *line<='9'; line++) {
        // Truncate any numerator too large to fit into an int64_t
        if (num > (INT64_MAX - 9) / 10 || den > INT64_MAX / 10)
            break;
        num  = 10 * num + *line - '0';
        den *= 10;
    }
    if (!num)
        *error = -1;
    av_reduce(&result.num, &result.den, num, den, 0x7FFFFFFF);
    return result;
}

115
static int rpl_read_header(AVFormatContext *s)
Eli Friedman's avatar
Eli Friedman committed
116
{
117
    AVIOContext *pb = s->pb;
Eli Friedman's avatar
Eli Friedman committed
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
    RPLContext *rpl = s->priv_data;
    AVStream *vst = NULL, *ast = NULL;
    int total_audio_size;
    int error = 0;

    uint32_t i;

    int32_t audio_format, chunk_catalog_offset, number_of_chunks;
    AVRational fps;

    char line[RPL_LINE_LENGTH];

    // The header for RPL/ARMovie files is 21 lines of text
    // containing the various header fields.  The fields are always
    // in the same order, and other text besides the first
    // number usually isn't important.
    // (The spec says that there exists some significance
    // for the text in a few cases; samples needed.)
136
    error |= read_line(pb, line, sizeof(line));      // ARMovie
137
    error |= read_line(pb, line, sizeof(line));      // movie name
138
    av_dict_set(&s->metadata, "title"    , line, 0);
139
    error |= read_line(pb, line, sizeof(line));      // date/copyright
140
    av_dict_set(&s->metadata, "copyright", line, 0);
141
    error |= read_line(pb, line, sizeof(line));      // author and other
142
    av_dict_set(&s->metadata, "author"   , line, 0);
Eli Friedman's avatar
Eli Friedman committed
143 144

    // video headers
145
    vst = avformat_new_stream(s, NULL);
Eli Friedman's avatar
Eli Friedman committed
146 147
    if (!vst)
        return AVERROR(ENOMEM);
148 149 150 151 152
    vst->codecpar->codec_type      = AVMEDIA_TYPE_VIDEO;
    vst->codecpar->codec_tag       = read_line_and_int(pb, &error);  // video format
    vst->codecpar->width           = read_line_and_int(pb, &error);  // video width
    vst->codecpar->height          = read_line_and_int(pb, &error);  // video height
    vst->codecpar->bits_per_coded_sample = read_line_and_int(pb, &error);  // video bits per sample
Eli Friedman's avatar
Eli Friedman committed
153 154
    error |= read_line(pb, line, sizeof(line));                   // video frames per second
    fps = read_fps(line, &error);
155
    avpriv_set_pts_info(vst, 32, fps.den, fps.num);
Eli Friedman's avatar
Eli Friedman committed
156 157

    // Figure out the video codec
158
    switch (vst->codecpar->codec_tag) {
Eli Friedman's avatar
Eli Friedman committed
159 160
#if 0
        case 122:
161
            vst->codecpar->codec_id = AV_CODEC_ID_ESCAPE122;
Eli Friedman's avatar
Eli Friedman committed
162 163 164
            break;
#endif
        case 124:
165
            vst->codecpar->codec_id = AV_CODEC_ID_ESCAPE124;
Eli Friedman's avatar
Eli Friedman committed
166
            // The header is wrong here, at least sometimes
167
            vst->codecpar->bits_per_coded_sample = 16;
Eli Friedman's avatar
Eli Friedman committed
168 169
            break;
        case 130:
170
            vst->codecpar->codec_id = AV_CODEC_ID_ESCAPE130;
Eli Friedman's avatar
Eli Friedman committed
171 172
            break;
        default:
173
            avpriv_report_missing_feature(s, "Video format %i",
174
                                          vst->codecpar->codec_tag);
175
            vst->codecpar->codec_id = AV_CODEC_ID_NONE;
Eli Friedman's avatar
Eli Friedman committed
176 177 178 179 180 181 182 183
    }

    // Audio headers

    // ARMovie supports multiple audio tracks; I don't have any
    // samples, though. This code will ignore additional tracks.
    audio_format = read_line_and_int(pb, &error);  // audio format ID
    if (audio_format) {
184
        ast = avformat_new_stream(s, NULL);
Eli Friedman's avatar
Eli Friedman committed
185 186
        if (!ast)
            return AVERROR(ENOMEM);
187 188 189 190 191
        ast->codecpar->codec_type      = AVMEDIA_TYPE_AUDIO;
        ast->codecpar->codec_tag       = audio_format;
        ast->codecpar->sample_rate     = read_line_and_int(pb, &error);  // audio bitrate
        ast->codecpar->channels        = read_line_and_int(pb, &error);  // number of audio channels
        ast->codecpar->bits_per_coded_sample = read_line_and_int(pb, &error);  // audio bits per sample
Eli Friedman's avatar
Eli Friedman committed
192 193
        // At least one sample uses 0 for ADPCM, which is really 4 bits
        // per sample.
194 195
        if (ast->codecpar->bits_per_coded_sample == 0)
            ast->codecpar->bits_per_coded_sample = 4;
Eli Friedman's avatar
Eli Friedman committed
196

197 198 199
        ast->codecpar->bit_rate = ast->codecpar->sample_rate *
                                  ast->codecpar->bits_per_coded_sample *
                                  ast->codecpar->channels;
Eli Friedman's avatar
Eli Friedman committed
200

201
        ast->codecpar->codec_id = AV_CODEC_ID_NONE;
Eli Friedman's avatar
Eli Friedman committed
202 203
        switch (audio_format) {
            case 1:
204
                if (ast->codecpar->bits_per_coded_sample == 16) {
Eli Friedman's avatar
Eli Friedman committed
205
                    // 16-bit audio is always signed
206
                    ast->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE;
Eli Friedman's avatar
Eli Friedman committed
207 208 209 210 211 212
                    break;
                }
                // There are some other formats listed as legal per the spec;
                // samples needed.
                break;
            case 101:
213
                if (ast->codecpar->bits_per_coded_sample == 8) {
Eli Friedman's avatar
Eli Friedman committed
214 215
                    // The samples with this kind of audio that I have
                    // are all unsigned.
216
                    ast->codecpar->codec_id = AV_CODEC_ID_PCM_U8;
Eli Friedman's avatar
Eli Friedman committed
217
                    break;
218 219
                } else if (ast->codecpar->bits_per_coded_sample == 4) {
                    ast->codecpar->codec_id = AV_CODEC_ID_ADPCM_IMA_EA_SEAD;
Eli Friedman's avatar
Eli Friedman committed
220 221 222 223
                    break;
                }
                break;
        }
224
        if (ast->codecpar->codec_id == AV_CODEC_ID_NONE)
225 226
            avpriv_request_sample(s, "Audio format %"PRId32,
                                  audio_format);
227
        avpriv_set_pts_info(ast, 32, 1, ast->codecpar->bit_rate);
Eli Friedman's avatar
Eli Friedman committed
228 229 230 231 232 233
    } else {
        for (i = 0; i < 3; i++)
            error |= read_line(pb, line, sizeof(line));
    }

    rpl->frames_per_chunk = read_line_and_int(pb, &error);  // video frames per chunk
234
    if (rpl->frames_per_chunk > 1 && vst->codecpar->codec_tag != 124)
Eli Friedman's avatar
Eli Friedman committed
235 236
        av_log(s, AV_LOG_WARNING,
               "Don't know how to split frames for video format %i. "
237
               "Video stream will be broken!\n", vst->codecpar->codec_tag);
Eli Friedman's avatar
Eli Friedman committed
238 239 240 241 242 243 244 245 246 247 248 249 250 251

    number_of_chunks = read_line_and_int(pb, &error);  // number of chunks in the file
    // The number in the header is actually the index of the last chunk.
    number_of_chunks++;

    error |= read_line(pb, line, sizeof(line));  // "even" chunk size in bytes
    error |= read_line(pb, line, sizeof(line));  // "odd" chunk size in bytes
    chunk_catalog_offset =                       // offset of the "chunk catalog"
        read_line_and_int(pb, &error);           //   (file index)
    error |= read_line(pb, line, sizeof(line));  // offset to "helpful" sprite
    error |= read_line(pb, line, sizeof(line));  // size of "helpful" sprite
    error |= read_line(pb, line, sizeof(line));  // offset to key frame list

    // Read the index
252
    avio_seek(pb, chunk_catalog_offset, SEEK_SET);
Eli Friedman's avatar
Eli Friedman committed
253
    total_audio_size = 0;
254
    for (i = 0; !error && i < number_of_chunks; i++) {
Eli Friedman's avatar
Eli Friedman committed
255 256
        int64_t offset, video_size, audio_size;
        error |= read_line(pb, line, sizeof(line));
257
        if (3 != sscanf(line, "%"SCNd64" , %"SCNd64" ; %"SCNd64,
258
                        &offset, &video_size, &audio_size)) {
Eli Friedman's avatar
Eli Friedman committed
259
            error = -1;
260 261
            continue;
        }
Eli Friedman's avatar
Eli Friedman committed
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
        av_add_index_entry(vst, offset, i * rpl->frames_per_chunk,
                           video_size, rpl->frames_per_chunk, 0);
        if (ast)
            av_add_index_entry(ast, offset + video_size, total_audio_size,
                               audio_size, audio_size * 8, 0);
        total_audio_size += audio_size * 8;
    }

    if (error) return AVERROR(EIO);

    return 0;
}

static int rpl_read_packet(AVFormatContext *s, AVPacket *pkt)
{
    RPLContext *rpl = s->priv_data;
278
    AVIOContext *pb = s->pb;
Eli Friedman's avatar
Eli Friedman committed
279 280
    AVStream* stream;
    AVIndexEntry* index_entry;
281
    int ret;
Eli Friedman's avatar
Eli Friedman committed
282 283 284 285 286 287 288 289 290

    if (rpl->chunk_part == s->nb_streams) {
        rpl->chunk_number++;
        rpl->chunk_part = 0;
    }

    stream = s->streams[rpl->chunk_part];

    if (rpl->chunk_number >= stream->nb_index_entries)
291
        return AVERROR_EOF;
Eli Friedman's avatar
Eli Friedman committed
292 293 294 295

    index_entry = &stream->index_entries[rpl->chunk_number];

    if (rpl->frame_in_part == 0)
296
        if (avio_seek(pb, index_entry->pos, SEEK_SET) < 0)
Eli Friedman's avatar
Eli Friedman committed
297 298
            return AVERROR(EIO);

299 300
    if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
        stream->codecpar->codec_tag == 124) {
Eli Friedman's avatar
Eli Friedman committed
301 302
        // We have to split Escape 124 frames because there are
        // multiple frames per chunk in Escape 124 samples.
Mans Rullgard's avatar
Mans Rullgard committed
303
        uint32_t frame_size;
Eli Friedman's avatar
Eli Friedman committed
304

Mans Rullgard's avatar
Mans Rullgard committed
305
        avio_skip(pb, 4); /* flags */
306
        frame_size = avio_rl32(pb);
307
        if (avio_seek(pb, -8, SEEK_CUR) < 0)
Eli Friedman's avatar
Eli Friedman committed
308 309 310
            return AVERROR(EIO);

        ret = av_get_packet(pb, pkt, frame_size);
311 312
        if (ret < 0)
            return ret;
Eli Friedman's avatar
Eli Friedman committed
313
        if (ret != frame_size) {
314
            av_packet_unref(pkt);
Eli Friedman's avatar
Eli Friedman committed
315 316 317 318 319 320 321 322 323 324 325 326 327
            return AVERROR(EIO);
        }
        pkt->duration = 1;
        pkt->pts = index_entry->timestamp + rpl->frame_in_part;
        pkt->stream_index = rpl->chunk_part;

        rpl->frame_in_part++;
        if (rpl->frame_in_part == rpl->frames_per_chunk) {
            rpl->frame_in_part = 0;
            rpl->chunk_part++;
        }
    } else {
        ret = av_get_packet(pb, pkt, index_entry->size);
328 329
        if (ret < 0)
            return ret;
Eli Friedman's avatar
Eli Friedman committed
330
        if (ret != index_entry->size) {
331
            av_packet_unref(pkt);
Eli Friedman's avatar
Eli Friedman committed
332 333 334
            return AVERROR(EIO);
        }

335
        if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
Eli Friedman's avatar
Eli Friedman committed
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
            // frames_per_chunk should always be one here; the header
            // parsing will warn if it isn't.
            pkt->duration = rpl->frames_per_chunk;
        } else {
            // All the audio codecs supported in this container
            // (at least so far) are constant-bitrate.
            pkt->duration = ret * 8;
        }
        pkt->pts = index_entry->timestamp;
        pkt->stream_index = rpl->chunk_part;
        rpl->chunk_part++;
    }

    // None of the Escape formats have keyframes, and the ADPCM
    // format used doesn't have keyframes.
    if (rpl->chunk_number == 0 && rpl->frame_in_part == 0)
352
        pkt->flags |= AV_PKT_FLAG_KEY;
Eli Friedman's avatar
Eli Friedman committed
353 354 355 356

    return ret;
}

357
AVInputFormat ff_rpl_demuxer = {
358
    .name           = "rpl",
359
    .long_name      = NULL_IF_CONFIG_SMALL("RPL / ARMovie"),
360 361 362 363
    .priv_data_size = sizeof(RPLContext),
    .read_probe     = rpl_probe,
    .read_header    = rpl_read_header,
    .read_packet    = rpl_read_packet,
Eli Friedman's avatar
Eli Friedman committed
364
};