1/*
2 * Interplay MVE File Demuxer
3 * Copyright (c) 2003 The ffmpeg Project
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
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
20 */
21
22/**
23 * @file
24 * Interplay MVE file demuxer
25 * by Mike Melanson (melanson@pcisys.net)
26 * For more information regarding the Interplay MVE file format, visit:
27 *   http://www.pcisys.net/~melanson/codecs/
28 * The aforementioned site also contains a command line utility for parsing
29 * IP MVE files so that you can get a good idea of the typical structure of
30 * such files. This demuxer is not the best example to use if you are trying
31 * to write your own as it uses a rather roundabout approach for splitting
32 * up and sending out the chunks.
33 */
34
35#include "libavutil/intreadwrite.h"
36#include "avformat.h"
37
38/* debugging support: #define DEBUG_IPMOVIE as non-zero to see extremely
39 * verbose information about the demux process */
40#define DEBUG_IPMOVIE 0
41
42#if DEBUG_IPMOVIE
43#undef printf
44#define debug_ipmovie printf
45#else
46static inline void debug_ipmovie(const char *format, ...) { }
47#endif
48
49#define CHUNK_PREAMBLE_SIZE 4
50#define OPCODE_PREAMBLE_SIZE 4
51
52#define CHUNK_INIT_AUDIO   0x0000
53#define CHUNK_AUDIO_ONLY   0x0001
54#define CHUNK_INIT_VIDEO   0x0002
55#define CHUNK_VIDEO        0x0003
56#define CHUNK_SHUTDOWN     0x0004
57#define CHUNK_END          0x0005
58/* these last types are used internally */
59#define CHUNK_DONE         0xFFFC
60#define CHUNK_NOMEM        0xFFFD
61#define CHUNK_EOF          0xFFFE
62#define CHUNK_BAD          0xFFFF
63
64#define OPCODE_END_OF_STREAM           0x00
65#define OPCODE_END_OF_CHUNK            0x01
66#define OPCODE_CREATE_TIMER            0x02
67#define OPCODE_INIT_AUDIO_BUFFERS      0x03
68#define OPCODE_START_STOP_AUDIO        0x04
69#define OPCODE_INIT_VIDEO_BUFFERS      0x05
70#define OPCODE_UNKNOWN_06              0x06
71#define OPCODE_SEND_BUFFER             0x07
72#define OPCODE_AUDIO_FRAME             0x08
73#define OPCODE_SILENCE_FRAME           0x09
74#define OPCODE_INIT_VIDEO_MODE         0x0A
75#define OPCODE_CREATE_GRADIENT         0x0B
76#define OPCODE_SET_PALETTE             0x0C
77#define OPCODE_SET_PALETTE_COMPRESSED  0x0D
78#define OPCODE_UNKNOWN_0E              0x0E
79#define OPCODE_SET_DECODING_MAP        0x0F
80#define OPCODE_UNKNOWN_10              0x10
81#define OPCODE_VIDEO_DATA              0x11
82#define OPCODE_UNKNOWN_12              0x12
83#define OPCODE_UNKNOWN_13              0x13
84#define OPCODE_UNKNOWN_14              0x14
85#define OPCODE_UNKNOWN_15              0x15
86
87#define PALETTE_COUNT 256
88
89typedef struct IPMVEContext {
90
91    unsigned char *buf;
92    int buf_size;
93
94    uint64_t frame_pts_inc;
95
96    unsigned int video_bpp;
97    unsigned int video_width;
98    unsigned int video_height;
99    int64_t video_pts;
100
101    unsigned int audio_bits;
102    unsigned int audio_channels;
103    unsigned int audio_sample_rate;
104    enum CodecID audio_type;
105    unsigned int audio_frame_count;
106
107    int video_stream_index;
108    int audio_stream_index;
109
110    int64_t audio_chunk_offset;
111    int audio_chunk_size;
112    int64_t video_chunk_offset;
113    int video_chunk_size;
114    int64_t decode_map_chunk_offset;
115    int decode_map_chunk_size;
116
117    int64_t next_chunk_offset;
118
119    AVPaletteControl palette_control;
120
121} IPMVEContext;
122
123static int load_ipmovie_packet(IPMVEContext *s, ByteIOContext *pb,
124    AVPacket *pkt) {
125
126    int chunk_type;
127
128    if (s->audio_chunk_offset) {
129
130        /* adjust for PCM audio by skipping chunk header */
131        if (s->audio_type != CODEC_ID_INTERPLAY_DPCM) {
132            s->audio_chunk_offset += 6;
133            s->audio_chunk_size -= 6;
134        }
135
136        url_fseek(pb, s->audio_chunk_offset, SEEK_SET);
137        s->audio_chunk_offset = 0;
138
139        if (s->audio_chunk_size != av_get_packet(pb, pkt, s->audio_chunk_size))
140            return CHUNK_EOF;
141
142        pkt->stream_index = s->audio_stream_index;
143        pkt->pts = s->audio_frame_count;
144
145        /* audio frame maintenance */
146        if (s->audio_type != CODEC_ID_INTERPLAY_DPCM)
147            s->audio_frame_count +=
148            (s->audio_chunk_size / s->audio_channels / (s->audio_bits / 8));
149        else
150            s->audio_frame_count +=
151                (s->audio_chunk_size - 6) / s->audio_channels;
152
153        debug_ipmovie("sending audio frame with pts %"PRId64" (%d audio frames)\n",
154            pkt->pts, s->audio_frame_count);
155
156        chunk_type = CHUNK_VIDEO;
157
158    } else if (s->decode_map_chunk_offset) {
159
160        /* send both the decode map and the video data together */
161
162        if (av_new_packet(pkt, s->decode_map_chunk_size + s->video_chunk_size))
163            return CHUNK_NOMEM;
164
165        pkt->pos= s->decode_map_chunk_offset;
166        url_fseek(pb, s->decode_map_chunk_offset, SEEK_SET);
167        s->decode_map_chunk_offset = 0;
168
169        if (get_buffer(pb, pkt->data, s->decode_map_chunk_size) !=
170            s->decode_map_chunk_size) {
171            av_free_packet(pkt);
172            return CHUNK_EOF;
173        }
174
175        url_fseek(pb, s->video_chunk_offset, SEEK_SET);
176        s->video_chunk_offset = 0;
177
178        if (get_buffer(pb, pkt->data + s->decode_map_chunk_size,
179            s->video_chunk_size) != s->video_chunk_size) {
180            av_free_packet(pkt);
181            return CHUNK_EOF;
182        }
183
184        pkt->stream_index = s->video_stream_index;
185        pkt->pts = s->video_pts;
186
187        debug_ipmovie("sending video frame with pts %"PRId64"\n",
188            pkt->pts);
189
190        s->video_pts += s->frame_pts_inc;
191
192        chunk_type = CHUNK_VIDEO;
193
194    } else {
195
196        url_fseek(pb, s->next_chunk_offset, SEEK_SET);
197        chunk_type = CHUNK_DONE;
198
199    }
200
201    return chunk_type;
202}
203
204/* This function loads and processes a single chunk in an IP movie file.
205 * It returns the type of chunk that was processed. */
206static int process_ipmovie_chunk(IPMVEContext *s, ByteIOContext *pb,
207    AVPacket *pkt)
208{
209    unsigned char chunk_preamble[CHUNK_PREAMBLE_SIZE];
210    int chunk_type;
211    int chunk_size;
212    unsigned char opcode_preamble[OPCODE_PREAMBLE_SIZE];
213    unsigned char opcode_type;
214    unsigned char opcode_version;
215    int opcode_size;
216    unsigned char scratch[1024];
217    int i, j;
218    int first_color, last_color;
219    int audio_flags;
220    unsigned char r, g, b;
221
222    /* see if there are any pending packets */
223    chunk_type = load_ipmovie_packet(s, pb, pkt);
224    if (chunk_type != CHUNK_DONE)
225        return chunk_type;
226
227    /* read the next chunk, wherever the file happens to be pointing */
228    if (url_feof(pb))
229        return CHUNK_EOF;
230    if (get_buffer(pb, chunk_preamble, CHUNK_PREAMBLE_SIZE) !=
231        CHUNK_PREAMBLE_SIZE)
232        return CHUNK_BAD;
233    chunk_size = AV_RL16(&chunk_preamble[0]);
234    chunk_type = AV_RL16(&chunk_preamble[2]);
235
236    debug_ipmovie("chunk type 0x%04X, 0x%04X bytes: ", chunk_type, chunk_size);
237
238    switch (chunk_type) {
239
240    case CHUNK_INIT_AUDIO:
241        debug_ipmovie("initialize audio\n");
242        break;
243
244    case CHUNK_AUDIO_ONLY:
245        debug_ipmovie("audio only\n");
246        break;
247
248    case CHUNK_INIT_VIDEO:
249        debug_ipmovie("initialize video\n");
250        break;
251
252    case CHUNK_VIDEO:
253        debug_ipmovie("video (and audio)\n");
254        break;
255
256    case CHUNK_SHUTDOWN:
257        debug_ipmovie("shutdown\n");
258        break;
259
260    case CHUNK_END:
261        debug_ipmovie("end\n");
262        break;
263
264    default:
265        debug_ipmovie("invalid chunk\n");
266        chunk_type = CHUNK_BAD;
267        break;
268
269    }
270
271    while ((chunk_size > 0) && (chunk_type != CHUNK_BAD)) {
272
273        /* read the next chunk, wherever the file happens to be pointing */
274       if (url_feof(pb)) {
275            chunk_type = CHUNK_EOF;
276            break;
277        }
278        if (get_buffer(pb, opcode_preamble, CHUNK_PREAMBLE_SIZE) !=
279            CHUNK_PREAMBLE_SIZE) {
280            chunk_type = CHUNK_BAD;
281            break;
282        }
283
284        opcode_size = AV_RL16(&opcode_preamble[0]);
285        opcode_type = opcode_preamble[2];
286        opcode_version = opcode_preamble[3];
287
288        chunk_size -= OPCODE_PREAMBLE_SIZE;
289        chunk_size -= opcode_size;
290        if (chunk_size < 0) {
291            debug_ipmovie("chunk_size countdown just went negative\n");
292            chunk_type = CHUNK_BAD;
293            break;
294        }
295
296        debug_ipmovie("  opcode type %02X, version %d, 0x%04X bytes: ",
297            opcode_type, opcode_version, opcode_size);
298        switch (opcode_type) {
299
300        case OPCODE_END_OF_STREAM:
301            debug_ipmovie("end of stream\n");
302            url_fseek(pb, opcode_size, SEEK_CUR);
303            break;
304
305        case OPCODE_END_OF_CHUNK:
306            debug_ipmovie("end of chunk\n");
307            url_fseek(pb, opcode_size, SEEK_CUR);
308            break;
309
310        case OPCODE_CREATE_TIMER:
311            debug_ipmovie("create timer\n");
312            if ((opcode_version > 0) || (opcode_size > 6)) {
313                debug_ipmovie("bad create_timer opcode\n");
314                chunk_type = CHUNK_BAD;
315                break;
316            }
317            if (get_buffer(pb, scratch, opcode_size) !=
318                opcode_size) {
319                chunk_type = CHUNK_BAD;
320                break;
321            }
322            s->frame_pts_inc = ((uint64_t)AV_RL32(&scratch[0])) * AV_RL16(&scratch[4]);
323            debug_ipmovie("  %.2f frames/second (timer div = %d, subdiv = %d)\n",
324                1000000.0/s->frame_pts_inc, AV_RL32(&scratch[0]), AV_RL16(&scratch[4]));
325            break;
326
327        case OPCODE_INIT_AUDIO_BUFFERS:
328            debug_ipmovie("initialize audio buffers\n");
329            if ((opcode_version > 1) || (opcode_size > 10)) {
330                debug_ipmovie("bad init_audio_buffers opcode\n");
331                chunk_type = CHUNK_BAD;
332                break;
333            }
334            if (get_buffer(pb, scratch, opcode_size) !=
335                opcode_size) {
336                chunk_type = CHUNK_BAD;
337                break;
338            }
339            s->audio_sample_rate = AV_RL16(&scratch[4]);
340            audio_flags = AV_RL16(&scratch[2]);
341            /* bit 0 of the flags: 0 = mono, 1 = stereo */
342            s->audio_channels = (audio_flags & 1) + 1;
343            /* bit 1 of the flags: 0 = 8 bit, 1 = 16 bit */
344            s->audio_bits = (((audio_flags >> 1) & 1) + 1) * 8;
345            /* bit 2 indicates compressed audio in version 1 opcode */
346            if ((opcode_version == 1) && (audio_flags & 0x4))
347                s->audio_type = CODEC_ID_INTERPLAY_DPCM;
348            else if (s->audio_bits == 16)
349                s->audio_type = CODEC_ID_PCM_S16LE;
350            else
351                s->audio_type = CODEC_ID_PCM_U8;
352            debug_ipmovie("audio: %d bits, %d Hz, %s, %s format\n",
353                s->audio_bits,
354                s->audio_sample_rate,
355                (s->audio_channels == 2) ? "stereo" : "mono",
356                (s->audio_type == CODEC_ID_INTERPLAY_DPCM) ?
357                "Interplay audio" : "PCM");
358            break;
359
360        case OPCODE_START_STOP_AUDIO:
361            debug_ipmovie("start/stop audio\n");
362            url_fseek(pb, opcode_size, SEEK_CUR);
363            break;
364
365        case OPCODE_INIT_VIDEO_BUFFERS:
366            debug_ipmovie("initialize video buffers\n");
367            if ((opcode_version > 2) || (opcode_size > 8)) {
368                debug_ipmovie("bad init_video_buffers opcode\n");
369                chunk_type = CHUNK_BAD;
370                break;
371            }
372            if (get_buffer(pb, scratch, opcode_size) !=
373                opcode_size) {
374                chunk_type = CHUNK_BAD;
375                break;
376            }
377            s->video_width = AV_RL16(&scratch[0]) * 8;
378            s->video_height = AV_RL16(&scratch[2]) * 8;
379            if (opcode_version < 2 || !AV_RL16(&scratch[6])) {
380                s->video_bpp = 8;
381            } else {
382                s->video_bpp = 16;
383            }
384            debug_ipmovie("video resolution: %d x %d\n",
385                s->video_width, s->video_height);
386            break;
387
388        case OPCODE_UNKNOWN_06:
389        case OPCODE_UNKNOWN_0E:
390        case OPCODE_UNKNOWN_10:
391        case OPCODE_UNKNOWN_12:
392        case OPCODE_UNKNOWN_13:
393        case OPCODE_UNKNOWN_14:
394        case OPCODE_UNKNOWN_15:
395            debug_ipmovie("unknown (but documented) opcode %02X\n", opcode_type);
396            url_fseek(pb, opcode_size, SEEK_CUR);
397            break;
398
399        case OPCODE_SEND_BUFFER:
400            debug_ipmovie("send buffer\n");
401            url_fseek(pb, opcode_size, SEEK_CUR);
402            break;
403
404        case OPCODE_AUDIO_FRAME:
405            debug_ipmovie("audio frame\n");
406
407            /* log position and move on for now */
408            s->audio_chunk_offset = url_ftell(pb);
409            s->audio_chunk_size = opcode_size;
410            url_fseek(pb, opcode_size, SEEK_CUR);
411            break;
412
413        case OPCODE_SILENCE_FRAME:
414            debug_ipmovie("silence frame\n");
415            url_fseek(pb, opcode_size, SEEK_CUR);
416            break;
417
418        case OPCODE_INIT_VIDEO_MODE:
419            debug_ipmovie("initialize video mode\n");
420            url_fseek(pb, opcode_size, SEEK_CUR);
421            break;
422
423        case OPCODE_CREATE_GRADIENT:
424            debug_ipmovie("create gradient\n");
425            url_fseek(pb, opcode_size, SEEK_CUR);
426            break;
427
428        case OPCODE_SET_PALETTE:
429            debug_ipmovie("set palette\n");
430            /* check for the logical maximum palette size
431             * (3 * 256 + 4 bytes) */
432            if (opcode_size > 0x304) {
433                debug_ipmovie("demux_ipmovie: set_palette opcode too large\n");
434                chunk_type = CHUNK_BAD;
435                break;
436            }
437            if (get_buffer(pb, scratch, opcode_size) != opcode_size) {
438                chunk_type = CHUNK_BAD;
439                break;
440            }
441
442            /* load the palette into internal data structure */
443            first_color = AV_RL16(&scratch[0]);
444            last_color = first_color + AV_RL16(&scratch[2]) - 1;
445            /* sanity check (since they are 16 bit values) */
446            if ((first_color > 0xFF) || (last_color > 0xFF)) {
447                debug_ipmovie("demux_ipmovie: set_palette indexes out of range (%d -> %d)\n",
448                    first_color, last_color);
449                chunk_type = CHUNK_BAD;
450                break;
451            }
452            j = 4;  /* offset of first palette data */
453            for (i = first_color; i <= last_color; i++) {
454                /* the palette is stored as a 6-bit VGA palette, thus each
455                 * component is shifted up to a 8-bit range */
456                r = scratch[j++] * 4;
457                g = scratch[j++] * 4;
458                b = scratch[j++] * 4;
459                s->palette_control.palette[i] = (r << 16) | (g << 8) | (b);
460            }
461            /* indicate a palette change */
462            s->palette_control.palette_changed = 1;
463            break;
464
465        case OPCODE_SET_PALETTE_COMPRESSED:
466            debug_ipmovie("set palette compressed\n");
467            url_fseek(pb, opcode_size, SEEK_CUR);
468            break;
469
470        case OPCODE_SET_DECODING_MAP:
471            debug_ipmovie("set decoding map\n");
472
473            /* log position and move on for now */
474            s->decode_map_chunk_offset = url_ftell(pb);
475            s->decode_map_chunk_size = opcode_size;
476            url_fseek(pb, opcode_size, SEEK_CUR);
477            break;
478
479        case OPCODE_VIDEO_DATA:
480            debug_ipmovie("set video data\n");
481
482            /* log position and move on for now */
483            s->video_chunk_offset = url_ftell(pb);
484            s->video_chunk_size = opcode_size;
485            url_fseek(pb, opcode_size, SEEK_CUR);
486            break;
487
488        default:
489            debug_ipmovie("*** unknown opcode type\n");
490            chunk_type = CHUNK_BAD;
491            break;
492
493        }
494    }
495
496    /* make a note of where the stream is sitting */
497    s->next_chunk_offset = url_ftell(pb);
498
499    /* dispatch the first of any pending packets */
500    if ((chunk_type == CHUNK_VIDEO) || (chunk_type == CHUNK_AUDIO_ONLY))
501        chunk_type = load_ipmovie_packet(s, pb, pkt);
502
503    return chunk_type;
504}
505
506static const char signature[] = "Interplay MVE File\x1A\0\x1A";
507
508static int ipmovie_probe(AVProbeData *p)
509{
510    uint8_t *b = p->buf;
511    uint8_t *b_end = p->buf + p->buf_size - sizeof(signature);
512    do {
513        if (memcmp(b++, signature, sizeof(signature)) == 0)
514            return AVPROBE_SCORE_MAX;
515    } while (b < b_end);
516
517    return 0;
518}
519
520static int ipmovie_read_header(AVFormatContext *s,
521                               AVFormatParameters *ap)
522{
523    IPMVEContext *ipmovie = s->priv_data;
524    ByteIOContext *pb = s->pb;
525    AVPacket pkt;
526    AVStream *st;
527    unsigned char chunk_preamble[CHUNK_PREAMBLE_SIZE];
528    int chunk_type;
529    uint8_t signature_buffer[sizeof(signature)];
530
531    get_buffer(pb, signature_buffer, sizeof(signature_buffer));
532    while (memcmp(signature_buffer, signature, sizeof(signature))) {
533        memmove(signature_buffer, signature_buffer + 1, sizeof(signature_buffer) - 1);
534        signature_buffer[sizeof(signature_buffer) - 1] = get_byte(pb);
535        if (url_feof(pb))
536            return AVERROR_EOF;
537    }
538    /* initialize private context members */
539    ipmovie->video_pts = ipmovie->audio_frame_count = 0;
540    ipmovie->audio_chunk_offset = ipmovie->video_chunk_offset =
541    ipmovie->decode_map_chunk_offset = 0;
542
543    /* on the first read, this will position the stream at the first chunk */
544    ipmovie->next_chunk_offset = url_ftell(pb) + 4;
545
546    /* process the first chunk which should be CHUNK_INIT_VIDEO */
547    if (process_ipmovie_chunk(ipmovie, pb, &pkt) != CHUNK_INIT_VIDEO)
548        return AVERROR_INVALIDDATA;
549
550    /* peek ahead to the next chunk-- if it is an init audio chunk, process
551     * it; if it is the first video chunk, this is a silent file */
552    if (get_buffer(pb, chunk_preamble, CHUNK_PREAMBLE_SIZE) !=
553        CHUNK_PREAMBLE_SIZE)
554        return AVERROR(EIO);
555    chunk_type = AV_RL16(&chunk_preamble[2]);
556    url_fseek(pb, -CHUNK_PREAMBLE_SIZE, SEEK_CUR);
557
558    if (chunk_type == CHUNK_VIDEO)
559        ipmovie->audio_type = CODEC_ID_NONE;  /* no audio */
560    else if (process_ipmovie_chunk(ipmovie, pb, &pkt) != CHUNK_INIT_AUDIO)
561        return AVERROR_INVALIDDATA;
562
563    /* initialize the stream decoders */
564    st = av_new_stream(s, 0);
565    if (!st)
566        return AVERROR(ENOMEM);
567    av_set_pts_info(st, 63, 1, 1000000);
568    ipmovie->video_stream_index = st->index;
569    st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
570    st->codec->codec_id = CODEC_ID_INTERPLAY_VIDEO;
571    st->codec->codec_tag = 0;  /* no fourcc */
572    st->codec->width = ipmovie->video_width;
573    st->codec->height = ipmovie->video_height;
574    st->codec->bits_per_coded_sample = ipmovie->video_bpp;
575
576    /* palette considerations */
577    st->codec->palctrl = &ipmovie->palette_control;
578
579    if (ipmovie->audio_type) {
580        st = av_new_stream(s, 0);
581        if (!st)
582            return AVERROR(ENOMEM);
583        av_set_pts_info(st, 32, 1, ipmovie->audio_sample_rate);
584        ipmovie->audio_stream_index = st->index;
585        st->codec->codec_type = AVMEDIA_TYPE_AUDIO;
586        st->codec->codec_id = ipmovie->audio_type;
587        st->codec->codec_tag = 0;  /* no tag */
588        st->codec->channels = ipmovie->audio_channels;
589        st->codec->sample_rate = ipmovie->audio_sample_rate;
590        st->codec->bits_per_coded_sample = ipmovie->audio_bits;
591        st->codec->bit_rate = st->codec->channels * st->codec->sample_rate *
592            st->codec->bits_per_coded_sample;
593        if (st->codec->codec_id == CODEC_ID_INTERPLAY_DPCM)
594            st->codec->bit_rate /= 2;
595        st->codec->block_align = st->codec->channels * st->codec->bits_per_coded_sample;
596    }
597
598    return 0;
599}
600
601static int ipmovie_read_packet(AVFormatContext *s,
602                               AVPacket *pkt)
603{
604    IPMVEContext *ipmovie = s->priv_data;
605    ByteIOContext *pb = s->pb;
606    int ret;
607
608    ret = process_ipmovie_chunk(ipmovie, pb, pkt);
609    if (ret == CHUNK_BAD)
610        ret = AVERROR_INVALIDDATA;
611    else if (ret == CHUNK_EOF)
612        ret = AVERROR(EIO);
613    else if (ret == CHUNK_NOMEM)
614        ret = AVERROR(ENOMEM);
615    else if (ret == CHUNK_VIDEO)
616        ret = 0;
617    else
618        ret = -1;
619
620    return ret;
621}
622
623AVInputFormat ipmovie_demuxer = {
624    "ipmovie",
625    NULL_IF_CONFIG_SMALL("Interplay MVE format"),
626    sizeof(IPMVEContext),
627    ipmovie_probe,
628    ipmovie_read_header,
629    ipmovie_read_packet,
630};
631