rpl.c 12.3 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 "libavutil/avstring.h"
23
#include "libavutil/dict.h"
Eli Friedman's avatar
Eli Friedman committed
24
#include "avformat.h"
25
#include "internal.h"
Eli Friedman's avatar
Eli Friedman committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
#include <stdlib.h>

#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;

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

81
static int32_t read_line_and_int(AVIOContext * pb, int* error)
Eli Friedman's avatar
Eli Friedman committed
82 83 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;
}

static int rpl_read_header(AVFormatContext *s, AVFormatParameters *ap)
{
115
    AVIOContext *pb = s->pb;
Eli Friedman's avatar
Eli Friedman committed
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
    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.)
134
    error |= read_line(pb, line, sizeof(line));      // ARMovie
135
    error |= read_line(pb, line, sizeof(line));      // movie name
136
    av_dict_set(&s->metadata, "title"    , line, 0);
137
    error |= read_line(pb, line, sizeof(line));      // date/copyright
138
    av_dict_set(&s->metadata, "copyright", line, 0);
139
    error |= read_line(pb, line, sizeof(line));      // author and other
140
    av_dict_set(&s->metadata, "author"   , line, 0);
Eli Friedman's avatar
Eli Friedman committed
141 142

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

    // Figure out the video codec
    switch (vst->codec->codec_tag) {
#if 0
        case 122:
            vst->codec->codec_id = CODEC_ID_ESCAPE122;
            break;
#endif
        case 124:
            vst->codec->codec_id = CODEC_ID_ESCAPE124;
            // The header is wrong here, at least sometimes
165
            vst->codec->bits_per_coded_sample = 16;
Eli Friedman's avatar
Eli Friedman committed
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
            break;
        case 130:
            vst->codec->codec_id = CODEC_ID_ESCAPE130;
            break;
        default:
            av_log(s, AV_LOG_WARNING,
                   "RPL video format %i not supported yet!\n",
                   vst->codec->codec_tag);
            vst->codec->codec_id = CODEC_ID_NONE;
    }

    // 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) {
183
        ast = avformat_new_stream(s, NULL);
Eli Friedman's avatar
Eli Friedman committed
184 185
        if (!ast)
            return AVERROR(ENOMEM);
186
        ast->codec->codec_type      = AVMEDIA_TYPE_AUDIO;
Eli Friedman's avatar
Eli Friedman committed
187 188 189
        ast->codec->codec_tag       = audio_format;
        ast->codec->sample_rate     = read_line_and_int(pb, &error);  // audio bitrate
        ast->codec->channels        = read_line_and_int(pb, &error);  // number of audio channels
190
        ast->codec->bits_per_coded_sample = read_line_and_int(pb, &error);  // audio bits per sample
Eli Friedman's avatar
Eli Friedman committed
191 192
        // At least one sample uses 0 for ADPCM, which is really 4 bits
        // per sample.
193 194
        if (ast->codec->bits_per_coded_sample == 0)
            ast->codec->bits_per_coded_sample = 4;
Eli Friedman's avatar
Eli Friedman committed
195 196

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

        ast->codec->codec_id = CODEC_ID_NONE;
        switch (audio_format) {
            case 1:
203
                if (ast->codec->bits_per_coded_sample == 16) {
Eli Friedman's avatar
Eli Friedman committed
204 205 206 207 208 209 210 211
                    // 16-bit audio is always signed
                    ast->codec->codec_id = CODEC_ID_PCM_S16LE;
                    break;
                }
                // There are some other formats listed as legal per the spec;
                // samples needed.
                break;
            case 101:
212
                if (ast->codec->bits_per_coded_sample == 8) {
Eli Friedman's avatar
Eli Friedman committed
213 214 215 216
                    // The samples with this kind of audio that I have
                    // are all unsigned.
                    ast->codec->codec_id = CODEC_ID_PCM_U8;
                    break;
217
                } else if (ast->codec->bits_per_coded_sample == 4) {
Eli Friedman's avatar
Eli Friedman committed
218 219 220 221 222 223 224 225 226 227
                    ast->codec->codec_id = CODEC_ID_ADPCM_IMA_EA_SEAD;
                    break;
                }
                break;
        }
        if (ast->codec->codec_id == CODEC_ID_NONE) {
            av_log(s, AV_LOG_WARNING,
                   "RPL audio format %i not supported yet!\n",
                   audio_format);
        }
228
        avpriv_set_pts_info(ast, 32, 1, ast->codec->bit_rate);
Eli Friedman's avatar
Eli Friedman committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
    } 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
    if (rpl->frames_per_chunk > 1 && vst->codec->codec_tag != 124)
        av_log(s, AV_LOG_WARNING,
               "Don't know how to split frames for video format %i. "
               "Video stream will be broken!\n", vst->codec->codec_tag);

    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
253
    avio_seek(pb, chunk_catalog_offset, SEEK_SET);
Eli Friedman's avatar
Eli Friedman committed
254
    total_audio_size = 0;
255
    for (i = 0; !error && i < number_of_chunks; i++) {
Eli Friedman's avatar
Eli Friedman committed
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
        int64_t offset, video_size, audio_size;
        error |= read_line(pb, line, sizeof(line));
        if (3 != sscanf(line, "%"PRId64" , %"PRId64" ; %"PRId64,
                        &offset, &video_size, &audio_size))
            error = -1;
        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;
277
    AVIOContext *pb = s->pb;
Eli Friedman's avatar
Eli Friedman committed
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
    AVStream* stream;
    AVIndexEntry* index_entry;
    uint32_t ret;

    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)
        return -1;

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

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

298
    if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO &&
Eli Friedman's avatar
Eli Friedman committed
299 300 301
        stream->codec->codec_tag == 124) {
        // 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
302
        uint32_t frame_size;
Eli Friedman's avatar
Eli Friedman committed
303

Mans Rullgard's avatar
Mans Rullgard committed
304
        avio_skip(pb, 4); /* flags */
305
        frame_size = avio_rl32(pb);
306
        if (avio_seek(pb, -8, SEEK_CUR) < 0)
Eli Friedman's avatar
Eli Friedman committed
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
            return AVERROR(EIO);

        ret = av_get_packet(pb, pkt, frame_size);
        if (ret != frame_size) {
            av_free_packet(pkt);
            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);
        if (ret != index_entry->size) {
            av_free_packet(pkt);
            return AVERROR(EIO);
        }

330
        if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
Eli Friedman's avatar
Eli Friedman committed
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
            // 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)
347
        pkt->flags |= AV_PKT_FLAG_KEY;
Eli Friedman's avatar
Eli Friedman committed
348 349 350 351

    return ret;
}

352
AVInputFormat ff_rpl_demuxer = {
353 354 355 356 357 358
    .name           = "rpl",
    .long_name      = NULL_IF_CONFIG_SMALL("RPL/ARMovie format"),
    .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
359
};