ffmdec.c 14.7 KB
Newer Older
Fabrice Bellard's avatar
Fabrice Bellard committed
1
/*
Baptiste Coudurier's avatar
Baptiste Coudurier committed
2
 * FFM (ffserver live feed) demuxer
3
 * Copyright (c) 2001 Fabrice Bellard.
Fabrice Bellard's avatar
Fabrice Bellard committed
4
 *
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.
Fabrice Bellard's avatar
Fabrice Bellard committed
11
 *
12
 * FFmpeg is distributed in the hope that it will be useful,
Fabrice Bellard's avatar
Fabrice Bellard committed
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
Fabrice Bellard's avatar
Fabrice Bellard committed
16
 *
17
 * 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
Fabrice Bellard's avatar
Fabrice Bellard committed
20
 */
Baptiste Coudurier's avatar
Baptiste Coudurier committed
21

Fabrice Bellard's avatar
Fabrice Bellard committed
22
#include "avformat.h"
Baptiste Coudurier's avatar
Baptiste Coudurier committed
23
#include "ffm.h"
24
#ifdef CONFIG_FFSERVER
Fabrice Bellard's avatar
Fabrice Bellard committed
25
#include <unistd.h>
Fabrice Bellard's avatar
Fabrice Bellard 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 52 53 54
offset_t ffm_read_write_index(int fd)
{
    uint8_t buf[8];

    lseek(fd, 8, SEEK_SET);
    read(fd, buf, 8);
    return AV_RB64(buf);
}

void ffm_write_write_index(int fd, offset_t pos)
{
    uint8_t buf[8];
    int i;

    for(i=0;i<8;i++)
        buf[i] = (pos >> (56 - i * 8)) & 0xff;
    lseek(fd, 8, SEEK_SET);
    write(fd, buf, 8);
}

void ffm_set_write_index(AVFormatContext *s, offset_t pos, offset_t file_size)
{
    FFMContext *ffm = s->priv_data;
    ffm->write_index = pos;
    ffm->file_size = file_size;
}
#endif // CONFIG_FFSERVER

Fabrice Bellard's avatar
Fabrice Bellard committed
55 56 57 58 59 60 61
static int ffm_is_avail_data(AVFormatContext *s, int size)
{
    FFMContext *ffm = s->priv_data;
    offset_t pos, avail_size;
    int len;

    len = ffm->packet_end - ffm->packet_ptr;
62 63
    if (size <= len)
        return 1;
64
    pos = url_ftell(s->pb);
Fabrice Bellard's avatar
Fabrice Bellard committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
    if (pos == ffm->write_index) {
        /* exactly at the end of stream */
        return 0;
    } else if (pos < ffm->write_index) {
        avail_size = ffm->write_index - pos;
    } else {
        avail_size = (ffm->file_size - pos) + (ffm->write_index - FFM_PACKET_SIZE);
    }
    avail_size = (avail_size / ffm->packet_size) * (ffm->packet_size - FFM_HEADER_SIZE) + len;
    if (size <= avail_size)
        return 1;
    else
        return 0;
}

/* first is true if we read the frame header */
81
static int ffm_read_data(AVFormatContext *s,
82
                         uint8_t *buf, int size, int header)
Fabrice Bellard's avatar
Fabrice Bellard committed
83 84
{
    FFMContext *ffm = s->priv_data;
85
    ByteIOContext *pb = s->pb;
Fabrice Bellard's avatar
Fabrice Bellard committed
86 87 88 89 90 91
    int len, fill_size, size1, frame_offset;

    size1 = size;
    while (size > 0) {
    redo:
        len = ffm->packet_end - ffm->packet_ptr;
92 93
        if (len < 0)
            return -1;
Fabrice Bellard's avatar
Fabrice Bellard committed
94 95 96 97 98
        if (len > size)
            len = size;
        if (len == 0) {
            if (url_ftell(pb) == ffm->file_size)
                url_fseek(pb, ffm->packet_size, SEEK_SET);
99
    retry_read:
Fabrice Bellard's avatar
Fabrice Bellard committed
100 101
            get_be16(pb); /* PACKET_ID */
            fill_size = get_be16(pb);
Baptiste Coudurier's avatar
Baptiste Coudurier committed
102
            ffm->dts = get_be64(pb);
Fabrice Bellard's avatar
Fabrice Bellard committed
103 104 105
            frame_offset = get_be16(pb);
            get_buffer(pb, ffm->packet, ffm->packet_size - FFM_HEADER_SIZE);
            ffm->packet_end = ffm->packet + (ffm->packet_size - FFM_HEADER_SIZE - fill_size);
106
            if (ffm->packet_end < ffm->packet || frame_offset < 0)
107
                return -1;
Fabrice Bellard's avatar
Fabrice Bellard committed
108 109 110
            /* if first packet or resynchronization packet, we must
               handle it specifically */
            if (ffm->first_packet || (frame_offset & 0x8000)) {
111 112 113 114 115 116 117 118 119
                if (!frame_offset) {
                    /* This packet has no frame headers in it */
                    if (url_ftell(pb) >= ffm->packet_size * 3) {
                        url_fseek(pb, -ffm->packet_size * 2, SEEK_CUR);
                        goto retry_read;
                    }
                    /* This is bad, we cannot find a valid frame header */
                    return 0;
                }
Fabrice Bellard's avatar
Fabrice Bellard committed
120
                ffm->first_packet = 0;
Baptiste Coudurier's avatar
Baptiste Coudurier committed
121
                if ((frame_offset & 0x7fff) < FFM_HEADER_SIZE)
122
                    return -1;
Fabrice Bellard's avatar
Fabrice Bellard committed
123
                ffm->packet_ptr = ffm->packet + (frame_offset & 0x7fff) - FFM_HEADER_SIZE;
124
                if (!header)
Fabrice Bellard's avatar
Fabrice Bellard committed
125 126 127 128 129 130 131 132 133 134
                    break;
            } else {
                ffm->packet_ptr = ffm->packet;
            }
            goto redo;
        }
        memcpy(buf, ffm->packet_ptr, len);
        buf += len;
        ffm->packet_ptr += len;
        size -= len;
135
        header = 0;
Fabrice Bellard's avatar
Fabrice Bellard committed
136 137 138 139
    }
    return size1 - size;
}

140 141
//#define DEBUG_SEEK

142 143 144 145 146 147 148 149 150 151 152 153
/* pos is between 0 and file_size - FFM_PACKET_SIZE. It is translated
   by the write position inside this function */
static void ffm_seek1(AVFormatContext *s, offset_t pos1)
{
    FFMContext *ffm = s->priv_data;
    ByteIOContext *pb = s->pb;
    offset_t pos;

    pos = pos1 + ffm->write_index;
    if (pos >= ffm->file_size)
        pos -= (ffm->file_size - FFM_PACKET_SIZE);
#ifdef DEBUG_SEEK
154
    av_log(s, AV_LOG_DEBUG, "seek to %"PRIx64" -> %"PRIx64"\n", pos1, pos);
155 156 157 158
#endif
    url_fseek(pb, pos, SEEK_SET);
}

Baptiste Coudurier's avatar
Baptiste Coudurier committed
159
static int64_t get_dts(AVFormatContext *s, offset_t pos)
160 161
{
    ByteIOContext *pb = s->pb;
Baptiste Coudurier's avatar
Baptiste Coudurier committed
162
    int64_t dts;
163 164 165

    ffm_seek1(s, pos);
    url_fskip(pb, 4);
Baptiste Coudurier's avatar
Baptiste Coudurier committed
166
    dts = get_be64(pb);
167
#ifdef DEBUG_SEEK
168
    av_log(s, AV_LOG_DEBUG, "pts=%0.6f\n", pts / 1000000.0);
169
#endif
Baptiste Coudurier's avatar
Baptiste Coudurier committed
170
    return dts;
171
}
Fabrice Bellard's avatar
Fabrice Bellard committed
172

173 174 175
static void adjust_write_index(AVFormatContext *s)
{
    FFMContext *ffm = s->priv_data;
176
    ByteIOContext *pb = s->pb;
177 178 179 180 181 182 183 184 185 186
    int64_t pts;
    //offset_t orig_write_index = ffm->write_index;
    offset_t pos_min, pos_max;
    int64_t pts_start;
    offset_t ptr = url_ftell(pb);


    pos_min = 0;
    pos_max = ffm->file_size - 2 * FFM_PACKET_SIZE;

Baptiste Coudurier's avatar
Baptiste Coudurier committed
187
    pts_start = get_dts(s, pos_min);
188

Baptiste Coudurier's avatar
Baptiste Coudurier committed
189
    pts = get_dts(s, pos_max);
190

191
    if (pts - 100000 > pts_start)
192
        goto end;
193 194 195

    ffm->write_index = FFM_PACKET_SIZE;

Baptiste Coudurier's avatar
Baptiste Coudurier committed
196
    pts_start = get_dts(s, pos_min);
197

Baptiste Coudurier's avatar
Baptiste Coudurier committed
198
    pts = get_dts(s, pos_max);
199 200 201 202 203 204 205 206 207 208 209

    if (pts - 100000 <= pts_start) {
        while (1) {
            offset_t newpos;
            int64_t newpts;

            newpos = ((pos_max + pos_min) / (2 * FFM_PACKET_SIZE)) * FFM_PACKET_SIZE;

            if (newpos == pos_min)
                break;

Baptiste Coudurier's avatar
Baptiste Coudurier committed
210
            newpts = get_dts(s, newpos);
211 212 213 214 215 216 217 218 219 220 221

            if (newpts - 100000 <= pts) {
                pos_max = newpos;
                pts = newpts;
            } else {
                pos_min = newpos;
            }
        }
        ffm->write_index += pos_max;
    }

222
    //printf("Adjusted write index from %"PRId64" to %"PRId64": pts=%0.6f\n", orig_write_index, ffm->write_index, pts / 1000000.);
Baptiste Coudurier's avatar
Baptiste Coudurier committed
223
    //printf("pts range %0.6f - %0.6f\n", get_dts(s, 0) / 1000000. , get_dts(s, ffm->file_size - 2 * FFM_PACKET_SIZE) / 1000000. );
224

225
 end:
226 227 228 229
    url_fseek(pb, ptr, SEEK_SET);
}


Fabrice Bellard's avatar
Fabrice Bellard committed
230 231
static int ffm_read_header(AVFormatContext *s, AVFormatParameters *ap)
{
Fabrice Bellard's avatar
Fabrice Bellard committed
232
    FFMContext *ffm = s->priv_data;
Fabrice Bellard's avatar
Fabrice Bellard committed
233
    AVStream *st;
234
    ByteIOContext *pb = s->pb;
Fabrice Bellard's avatar
Fabrice Bellard committed
235
    AVCodecContext *codec;
236
    int i, nb_streams;
237
    uint32_t tag;
Fabrice Bellard's avatar
Fabrice Bellard committed
238 239 240 241 242 243 244 245 246 247 248

    /* header */
    tag = get_le32(pb);
    if (tag != MKTAG('F', 'F', 'M', '1'))
        goto fail;
    ffm->packet_size = get_be32(pb);
    if (ffm->packet_size != FFM_PACKET_SIZE)
        goto fail;
    ffm->write_index = get_be64(pb);
    /* get also filesize */
    if (!url_is_streamed(pb)) {
249
        ffm->file_size = url_fsize(pb);
250
        adjust_write_index(s);
Fabrice Bellard's avatar
Fabrice Bellard committed
251
    } else {
252
        ffm->file_size = (UINT64_C(1) << 63) - 1;
Fabrice Bellard's avatar
Fabrice Bellard committed
253 254
    }

255
    nb_streams = get_be32(pb);
Fabrice Bellard's avatar
Fabrice Bellard committed
256 257
    get_be32(pb); /* total bitrate */
    /* read each stream */
258
    for(i=0;i<nb_streams;i++) {
259 260
        char rc_eq_buf[128];

261
        st = av_new_stream(s, 0);
Fabrice Bellard's avatar
Fabrice Bellard committed
262 263
        if (!st)
            goto fail;
264
        s->streams[i] = st;
265

266
        av_set_pts_info(st, 64, 1, 1000000);
267

268
        codec = st->codec;
Fabrice Bellard's avatar
Fabrice Bellard committed
269
        /* generic info */
270 271
        codec->codec_id = get_be32(pb);
        codec->codec_type = get_byte(pb); /* codec_type */
Fabrice Bellard's avatar
Fabrice Bellard committed
272
        codec->bit_rate = get_be32(pb);
273
        st->quality = get_be32(pb);
274
        codec->flags = get_be32(pb);
275 276
        codec->flags2 = get_be32(pb);
        codec->debug = get_be32(pb);
Fabrice Bellard's avatar
Fabrice Bellard committed
277 278 279
        /* specific info */
        switch(codec->codec_type) {
        case CODEC_TYPE_VIDEO:
280 281
            codec->time_base.num = get_be32(pb);
            codec->time_base.den = get_be32(pb);
Fabrice Bellard's avatar
Fabrice Bellard committed
282 283
            codec->width = get_be16(pb);
            codec->height = get_be16(pb);
284
            codec->gop_size = get_be16(pb);
285
            codec->pix_fmt = get_be32(pb);
286 287 288 289 290
            codec->qmin = get_byte(pb);
            codec->qmax = get_byte(pb);
            codec->max_qdiff = get_byte(pb);
            codec->qcompress = get_be16(pb) / 10000.0;
            codec->qblur = get_be16(pb) / 10000.0;
291
            codec->bit_rate_tolerance = get_be32(pb);
Fabrice Bellard's avatar
Fabrice Bellard committed
292
            codec->rc_eq = av_strdup(get_strz(pb, rc_eq_buf, sizeof(rc_eq_buf)));
293 294 295
            codec->rc_max_rate = get_be32(pb);
            codec->rc_min_rate = get_be32(pb);
            codec->rc_buffer_size = get_be32(pb);
296 297 298 299
            codec->i_quant_factor = av_int2dbl(get_be64(pb));
            codec->b_quant_factor = av_int2dbl(get_be64(pb));
            codec->i_quant_offset = av_int2dbl(get_be64(pb));
            codec->b_quant_offset = av_int2dbl(get_be64(pb));
300
            codec->dct_algo = get_be32(pb);
301 302 303 304 305 306 307 308 309 310
            codec->strict_std_compliance = get_be32(pb);
            codec->max_b_frames = get_be32(pb);
            codec->luma_elim_threshold = get_be32(pb);
            codec->chroma_elim_threshold = get_be32(pb);
            codec->mpeg_quant = get_be32(pb);
            codec->intra_dc_precision = get_be32(pb);
            codec->me_method = get_be32(pb);
            codec->mb_decision = get_be32(pb);
            codec->nsse_weight = get_be32(pb);
            codec->frame_skip_cmp = get_be32(pb);
311
            codec->rc_buffer_aggressivity = av_int2dbl(get_be64(pb));
312
            codec->codec_tag = get_be32(pb);
313
            codec->thread_count = get_byte(pb);
Fabrice Bellard's avatar
Fabrice Bellard committed
314 315 316 317
            break;
        case CODEC_TYPE_AUDIO:
            codec->sample_rate = get_be32(pb);
            codec->channels = get_le16(pb);
318
            codec->frame_size = get_le16(pb);
Fabrice Bellard's avatar
Fabrice Bellard committed
319
            break;
320
        default:
321
            goto fail;
Fabrice Bellard's avatar
Fabrice Bellard committed
322
        }
323 324 325 326 327 328 329
        if (codec->flags & CODEC_FLAG_GLOBAL_HEADER) {
            codec->extradata_size = get_be32(pb);
            codec->extradata = av_malloc(codec->extradata_size);
            if (!codec->extradata)
                return AVERROR(ENOMEM);
            get_buffer(pb, codec->extradata, codec->extradata_size);
        }
Fabrice Bellard's avatar
Fabrice Bellard committed
330 331 332 333 334 335 336 337 338 339
    }

    /* get until end of block reached */
    while ((url_ftell(pb) % ffm->packet_size) != 0)
        get_byte(pb);

    /* init packet demux */
    ffm->packet_ptr = ffm->packet;
    ffm->packet_end = ffm->packet;
    ffm->frame_offset = 0;
Baptiste Coudurier's avatar
Baptiste Coudurier committed
340
    ffm->dts = 0;
Fabrice Bellard's avatar
Fabrice Bellard committed
341 342 343 344 345 346 347
    ffm->read_state = READ_HEADER;
    ffm->first_packet = 1;
    return 0;
 fail:
    for(i=0;i<s->nb_streams;i++) {
        st = s->streams[i];
        if (st) {
348
            av_free(st);
Fabrice Bellard's avatar
Fabrice Bellard committed
349 350 351 352 353 354 355 356 357 358
        }
    }
    return -1;
}

/* return < 0 if eof */
static int ffm_read_packet(AVFormatContext *s, AVPacket *pkt)
{
    int size;
    FFMContext *ffm = s->priv_data;
359
    int duration;
Fabrice Bellard's avatar
Fabrice Bellard committed
360 361 362

    switch(ffm->read_state) {
    case READ_HEADER:
363
        if (!ffm_is_avail_data(s, FRAME_HEADER_SIZE+4)) {
364
            return AVERROR(EAGAIN);
365
        }
Baptiste Coudurier's avatar
Baptiste Coudurier committed
366
        dprintf(s, "pos=%08"PRIx64" spos=%"PRIx64", write_index=%"PRIx64" size=%"PRIx64"\n",
367
               url_ftell(s->pb), s->pb->pos, ffm->write_index, ffm->file_size);
368
        if (ffm_read_data(s, ffm->header, FRAME_HEADER_SIZE, 1) !=
369
            FRAME_HEADER_SIZE)
370
            return AVERROR(EAGAIN);
371 372 373
        if (ffm->header[1] & FLAG_DTS)
            if (ffm_read_data(s, ffm->header+16, 4, 1) != 4)
                return AVERROR(EAGAIN);
Fabrice Bellard's avatar
Fabrice Bellard committed
374
#if 0
Baptiste Coudurier's avatar
Baptiste Coudurier committed
375
        av_hexdump_log(s, AV_LOG_DEBUG, ffm->header, FRAME_HEADER_SIZE);
Fabrice Bellard's avatar
Fabrice Bellard committed
376 377 378 379
#endif
        ffm->read_state = READ_DATA;
        /* fall thru */
    case READ_DATA:
380
        size = AV_RB24(ffm->header + 2);
Fabrice Bellard's avatar
Fabrice Bellard committed
381
        if (!ffm_is_avail_data(s, size)) {
382
            return AVERROR(EAGAIN);
Fabrice Bellard's avatar
Fabrice Bellard committed
383 384
        }

385
        duration = AV_RB24(ffm->header + 5);
386

Fabrice Bellard's avatar
Fabrice Bellard committed
387 388
        av_new_packet(pkt, size);
        pkt->stream_index = ffm->header[0];
389 390 391 392 393 394
        if ((unsigned)pkt->stream_index >= s->nb_streams) {
            av_log(s, AV_LOG_ERROR, "invalid stream index %d\n", pkt->stream_index);
            av_free_packet(pkt);
            ffm->read_state = READ_HEADER;
            return AVERROR(EAGAIN);
        }
395
        pkt->pos = url_ftell(s->pb);
Fabrice Bellard's avatar
Fabrice Bellard committed
396 397
        if (ffm->header[1] & FLAG_KEY_FRAME)
            pkt->flags |= PKT_FLAG_KEY;
398

Fabrice Bellard's avatar
Fabrice Bellard committed
399 400 401 402
        ffm->read_state = READ_HEADER;
        if (ffm_read_data(s, pkt->data, size, 0) != size) {
            /* bad case: desynchronized packet. we cancel all the packet loading */
            av_free_packet(pkt);
403
            return AVERROR(EAGAIN);
Fabrice Bellard's avatar
Fabrice Bellard committed
404
        }
405 406 407 408 409
        pkt->pts = AV_RB64(ffm->header+8);
        if (ffm->header[1] & FLAG_DTS)
            pkt->dts = pkt->pts - AV_RB32(ffm->header+16);
        else
            pkt->dts = pkt->pts;
410
        pkt->duration = duration;
Fabrice Bellard's avatar
Fabrice Bellard committed
411 412 413 414 415 416
        break;
    }
    return 0;
}

/* seek to a given time in the file. The file read pointer is
Diego Biurrun's avatar
Diego Biurrun committed
417
   positioned at or before pts. XXX: the following code is quite
Fabrice Bellard's avatar
Fabrice Bellard committed
418
   approximative */
419
static int ffm_seek(AVFormatContext *s, int stream_index, int64_t wanted_pts, int flags)
Fabrice Bellard's avatar
Fabrice Bellard committed
420 421 422
{
    FFMContext *ffm = s->priv_data;
    offset_t pos_min, pos_max, pos;
423
    int64_t pts_min, pts_max, pts;
Fabrice Bellard's avatar
Fabrice Bellard committed
424 425 426
    double pos1;

#ifdef DEBUG_SEEK
427
    av_log(s, AV_LOG_DEBUG, "wanted_pts=%0.6f\n", wanted_pts / 1000000.0);
Fabrice Bellard's avatar
Fabrice Bellard committed
428 429 430 431 432 433
#endif
    /* find the position using linear interpolation (better than
       dichotomy in typical cases) */
    pos_min = 0;
    pos_max = ffm->file_size - 2 * FFM_PACKET_SIZE;
    while (pos_min <= pos_max) {
Baptiste Coudurier's avatar
Baptiste Coudurier committed
434 435
        pts_min = get_dts(s, pos_min);
        pts_max = get_dts(s, pos_max);
Fabrice Bellard's avatar
Fabrice Bellard committed
436
        /* linear interpolation */
437
        pos1 = (double)(pos_max - pos_min) * (double)(wanted_pts - pts_min) /
Fabrice Bellard's avatar
Fabrice Bellard committed
438
            (double)(pts_max - pts_min);
439
        pos = (((int64_t)pos1) / FFM_PACKET_SIZE) * FFM_PACKET_SIZE;
Fabrice Bellard's avatar
Fabrice Bellard committed
440 441 442 443
        if (pos <= pos_min)
            pos = pos_min;
        else if (pos >= pos_max)
            pos = pos_max;
Baptiste Coudurier's avatar
Baptiste Coudurier committed
444
        pts = get_dts(s, pos);
Fabrice Bellard's avatar
Fabrice Bellard committed
445 446 447 448 449 450 451 452 453
        /* check if we are lucky */
        if (pts == wanted_pts) {
            goto found;
        } else if (pts > wanted_pts) {
            pos_max = pos - FFM_PACKET_SIZE;
        } else {
            pos_min = pos + FFM_PACKET_SIZE;
        }
    }
454
    pos = (flags & AVSEEK_FLAG_BACKWARD) ? pos_min : pos_max;
Fabrice Bellard's avatar
Fabrice Bellard committed
455 456 457 458
    if (pos > 0)
        pos -= FFM_PACKET_SIZE;
 found:
    ffm_seek1(s, pos);
459 460 461 462 463 464 465

    /* reset read state */
    ffm->read_state = READ_HEADER;
    ffm->packet_ptr = ffm->packet;
    ffm->packet_end = ffm->packet;
    ffm->first_packet = 1;

Fabrice Bellard's avatar
Fabrice Bellard committed
466 467 468
    return 0;
}

469 470
static int ffm_probe(AVProbeData *p)
{
471
    if (
472
        p->buf[0] == 'F' && p->buf[1] == 'F' && p->buf[2] == 'M' &&
473
        p->buf[3] == '1')
474 475 476 477
        return AVPROBE_SCORE_MAX + 1;
    return 0;
}

478
AVInputFormat ffm_demuxer = {
Fabrice Bellard's avatar
Fabrice Bellard committed
479
    "ffm",
480
    NULL_IF_CONFIG_SMALL("FFM (FFserver live feed) format"),
481
    sizeof(FFMContext),
482
    ffm_probe,
Fabrice Bellard's avatar
Fabrice Bellard committed
483 484
    ffm_read_header,
    ffm_read_packet,
485
    NULL,
Fabrice Bellard's avatar
Fabrice Bellard committed
486 487
    ffm_seek,
};