rl2.c 8.81 KB
Newer Older
Sascha Sommer's avatar
Sascha Sommer committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * RL2 Format Demuxer
 * Copyright (c) 2008 Sascha Sommer (saschasommer@freenet.de)
 *
 * 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
 */

/**
 * RL2 file demuxer
24
 * @file
Sascha Sommer's avatar
Sascha Sommer committed
25
 * @author Sascha Sommer (saschasommer@freenet.de)
26
 * @see http://wiki.multimedia.cx/index.php?title=RL2
Sascha Sommer's avatar
Sascha Sommer committed
27 28 29 30 31 32 33 34
 *
 * extradata:
 * 2 byte le initial drawing offset within 320x200 viewport
 * 4 byte le number of used colors
 * 256 * 3 bytes rgb palette
 * optional background_frame
 */

35
#include "libavutil/intreadwrite.h"
36
#include "libavutil/mathematics.h"
Sascha Sommer's avatar
Sascha Sommer committed
37
#include "avformat.h"
38
#include "internal.h"
Sascha Sommer's avatar
Sascha Sommer committed
39 40 41 42 43 44 45 46

#define EXTRADATA1_SIZE (6 + 256 * 3) ///< video base, clr, palette

#define FORM_TAG MKBETAG('F', 'O', 'R', 'M')
#define RLV2_TAG MKBETAG('R', 'L', 'V', '2')
#define RLV3_TAG MKBETAG('R', 'L', 'V', '3')

typedef struct Rl2DemuxContext {
47
    unsigned int index_pos[2];   ///< indexes in the sample tables
Sascha Sommer's avatar
Sascha Sommer committed
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
} Rl2DemuxContext;


/**
 * check if the file is in rl2 format
 * @param p probe buffer
 * @return 0 when the probe buffer does not contain rl2 data, > 0 otherwise
 */
static int rl2_probe(AVProbeData *p)
{

    if(AV_RB32(&p->buf[0]) != FORM_TAG)
        return 0;

    if(AV_RB32(&p->buf[8]) != RLV2_TAG &&
        AV_RB32(&p->buf[8]) != RLV3_TAG)
        return 0;

    return AVPROBE_SCORE_MAX;
}

/**
 * read rl2 header data and setup the avstreams
 * @param s demuxer context
 * @return 0 on success, AVERROR otherwise
 */
74
static av_cold int rl2_read_header(AVFormatContext *s)
Sascha Sommer's avatar
Sascha Sommer committed
75
{
76
    AVIOContext *pb = s->pb;
Sascha Sommer's avatar
Sascha Sommer committed
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
    AVStream *st;
    unsigned int frame_count;
    unsigned int audio_frame_counter = 0;
    unsigned int video_frame_counter = 0;
    unsigned int back_size;
    unsigned short sound_rate;
    unsigned short rate;
    unsigned short channels;
    unsigned short def_sound_size;
    unsigned int signature;
    unsigned int pts_den = 11025; /* video only case */
    unsigned int pts_num = 1103;
    unsigned int* chunk_offset = NULL;
    int* chunk_size = NULL;
    int* audio_size = NULL;
    int i;
    int ret = 0;

95
    avio_skip(pb,4);          /* skip FORM tag */
96 97
    back_size = avio_rl32(pb); /**< get size of the background frame */
    signature = avio_rb32(pb);
Mans Rullgard's avatar
Mans Rullgard committed
98
    avio_skip(pb, 4);         /* data size */
99
    frame_count = avio_rl32(pb);
Sascha Sommer's avatar
Sascha Sommer committed
100 101 102 103 104

    /* disallow back_sizes and frame_counts that may lead to overflows later */
    if(back_size > INT_MAX/2  || frame_count > INT_MAX / sizeof(uint32_t))
        return AVERROR_INVALIDDATA;

Mans Rullgard's avatar
Mans Rullgard committed
105
    avio_skip(pb, 2);         /* encoding mentod */
106 107 108 109
    sound_rate = avio_rl16(pb);
    rate = avio_rl16(pb);
    channels = avio_rl16(pb);
    def_sound_size = avio_rl16(pb);
Sascha Sommer's avatar
Sascha Sommer committed
110 111

    /** setup video stream */
112
    st = avformat_new_stream(s, NULL);
Sascha Sommer's avatar
Sascha Sommer committed
113 114 115
    if(!st)
         return AVERROR(ENOMEM);

116
    st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
117
    st->codec->codec_id = AV_CODEC_ID_RL2;
Sascha Sommer's avatar
Sascha Sommer committed
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
    st->codec->codec_tag = 0;  /* no fourcc */
    st->codec->width = 320;
    st->codec->height = 200;

    /** allocate and fill extradata */
    st->codec->extradata_size = EXTRADATA1_SIZE;

    if(signature == RLV3_TAG && back_size > 0)
        st->codec->extradata_size += back_size;

    st->codec->extradata = av_mallocz(st->codec->extradata_size +
                                          FF_INPUT_BUFFER_PADDING_SIZE);
    if(!st->codec->extradata)
        return AVERROR(ENOMEM);

133
    if(avio_read(pb,st->codec->extradata,st->codec->extradata_size) !=
Sascha Sommer's avatar
Sascha Sommer committed
134 135 136 137 138
                      st->codec->extradata_size)
        return AVERROR(EIO);

    /** setup audio stream if present */
    if(sound_rate){
Michael Niedermayer's avatar
Michael Niedermayer committed
139 140 141
        if(channels <= 0)
            return AVERROR_INVALIDDATA;

Sascha Sommer's avatar
Sascha Sommer committed
142 143 144
        pts_num = def_sound_size;
        pts_den = rate;

145
        st = avformat_new_stream(s, NULL);
Sascha Sommer's avatar
Sascha Sommer committed
146 147
        if (!st)
            return AVERROR(ENOMEM);
148
        st->codec->codec_type = AVMEDIA_TYPE_AUDIO;
149
        st->codec->codec_id = AV_CODEC_ID_PCM_U8;
Sascha Sommer's avatar
Sascha Sommer committed
150 151
        st->codec->codec_tag = 1;
        st->codec->channels = channels;
152
        st->codec->bits_per_coded_sample = 8;
Sascha Sommer's avatar
Sascha Sommer committed
153 154
        st->codec->sample_rate = rate;
        st->codec->bit_rate = st->codec->channels * st->codec->sample_rate *
155
            st->codec->bits_per_coded_sample;
Sascha Sommer's avatar
Sascha Sommer committed
156
        st->codec->block_align = st->codec->channels *
157
            st->codec->bits_per_coded_sample / 8;
158
        avpriv_set_pts_info(st,32,1,rate);
Sascha Sommer's avatar
Sascha Sommer committed
159 160
    }

161
    avpriv_set_pts_info(s->streams[0], 32, pts_num, pts_den);
Sascha Sommer's avatar
Sascha Sommer committed
162 163 164 165 166 167 168 169 170 171 172 173 174 175

    chunk_size =   av_malloc(frame_count * sizeof(uint32_t));
    audio_size =   av_malloc(frame_count * sizeof(uint32_t));
    chunk_offset = av_malloc(frame_count * sizeof(uint32_t));

    if(!chunk_size || !audio_size || !chunk_offset){
        av_free(chunk_size);
        av_free(audio_size);
        av_free(chunk_offset);
        return AVERROR(ENOMEM);
    }

    /** read offset and size tables */
    for(i=0; i < frame_count;i++)
176
        chunk_size[i] = avio_rl32(pb);
Sascha Sommer's avatar
Sascha Sommer committed
177
    for(i=0; i < frame_count;i++)
178
        chunk_offset[i] = avio_rl32(pb);
Sascha Sommer's avatar
Sascha Sommer committed
179
    for(i=0; i < frame_count;i++)
180
        audio_size[i] = avio_rl32(pb) & 0xFFFF;
Sascha Sommer's avatar
Sascha Sommer committed
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216

    /** build the sample index */
    for(i=0;i<frame_count;i++){
        if(chunk_size[i] < 0 || audio_size[i] > chunk_size[i]){
            ret = AVERROR_INVALIDDATA;
            break;
        }

        if(sound_rate && audio_size[i]){
            av_add_index_entry(s->streams[1], chunk_offset[i],
                audio_frame_counter,audio_size[i], 0, AVINDEX_KEYFRAME);
            audio_frame_counter += audio_size[i] / channels;
        }
        av_add_index_entry(s->streams[0], chunk_offset[i] + audio_size[i],
            video_frame_counter,chunk_size[i]-audio_size[i],0,AVINDEX_KEYFRAME);
        ++video_frame_counter;
    }


    av_free(chunk_size);
    av_free(audio_size);
    av_free(chunk_offset);

    return ret;
}

/**
 * read a single audio or video packet
 * @param s demuxer context
 * @param pkt the packet to be filled
 * @return 0 on success, AVERROR otherwise
 */
static int rl2_read_packet(AVFormatContext *s,
                            AVPacket *pkt)
{
    Rl2DemuxContext *rl2 = s->priv_data;
217
    AVIOContext *pb = s->pb;
Sascha Sommer's avatar
Sascha Sommer committed
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
    AVIndexEntry *sample = NULL;
    int i;
    int ret = 0;
    int stream_id = -1;
    int64_t pos = INT64_MAX;

    /** check if there is a valid video or audio entry that can be used */
    for(i=0; i<s->nb_streams; i++){
        if(rl2->index_pos[i] < s->streams[i]->nb_index_entries
              && s->streams[i]->index_entries[ rl2->index_pos[i] ].pos < pos){
            sample = &s->streams[i]->index_entries[ rl2->index_pos[i] ];
            pos= sample->pos;
            stream_id= i;
        }
    }

    if(stream_id == -1)
        return AVERROR(EIO);

    ++rl2->index_pos[stream_id];

    /** position the stream (will probably be there anyway) */
240
    avio_seek(pb, sample->pos, SEEK_SET);
Sascha Sommer's avatar
Sascha Sommer committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289

    /** fill the packet */
    ret = av_get_packet(pb, pkt, sample->size);
    if(ret != sample->size){
        av_free_packet(pkt);
        return AVERROR(EIO);
    }

    pkt->stream_index = stream_id;
    pkt->pts = sample->timestamp;

    return ret;
}

/**
 * seek to a new timestamp
 * @param s demuxer context
 * @param stream_index index of the stream that should be seeked
 * @param timestamp wanted timestamp
 * @param flags direction and seeking mode
 * @return 0 on success, -1 otherwise
 */
static int rl2_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
{
    AVStream *st = s->streams[stream_index];
    Rl2DemuxContext *rl2 = s->priv_data;
    int i;
    int index = av_index_search_timestamp(st, timestamp, flags);
    if(index < 0)
        return -1;

    rl2->index_pos[stream_index] = index;
    timestamp = st->index_entries[index].timestamp;

    for(i=0; i < s->nb_streams; i++){
        AVStream *st2 = s->streams[i];
        index = av_index_search_timestamp(st2,
                    av_rescale_q(timestamp, st->time_base, st2->time_base),
                    flags | AVSEEK_FLAG_BACKWARD);

        if(index < 0)
            index = 0;

        rl2->index_pos[i] = index;
    }

    return 0;
}

290
AVInputFormat ff_rl2_demuxer = {
291
    .name           = "rl2",
292
    .long_name      = NULL_IF_CONFIG_SMALL("RL2"),
293 294 295 296 297
    .priv_data_size = sizeof(Rl2DemuxContext),
    .read_probe     = rl2_probe,
    .read_header    = rl2_read_header,
    .read_packet    = rl2_read_packet,
    .read_seek      = rl2_read_seek,
Sascha Sommer's avatar
Sascha Sommer committed
298
};