]> git.sesse.net Git - ffmpeg/blob - libavformat/concatdec.c
Merge commit '832129431fd5c693b12c32a1563944c631feaf36'
[ffmpeg] / libavformat / concatdec.c
1 /*
2  * Copyright (c) 2012 Nicolas George
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public License
8  * as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with FFmpeg; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20
21 #include "libavutil/avassert.h"
22 #include "libavutil/avstring.h"
23 #include "libavutil/intreadwrite.h"
24 #include "libavutil/opt.h"
25 #include "libavutil/parseutils.h"
26 #include "libavutil/timestamp.h"
27 #include "avformat.h"
28 #include "internal.h"
29 #include "url.h"
30
31 typedef enum ConcatMatchMode {
32     MATCH_ONE_TO_ONE,
33     MATCH_EXACT_ID,
34 } ConcatMatchMode;
35
36 typedef struct ConcatStream {
37     AVBitStreamFilterContext *bsf;
38     int out_stream_index;
39 } ConcatStream;
40
41 typedef struct {
42     char *url;
43     int64_t start_time;
44     int64_t duration;
45     ConcatStream *streams;
46     int nb_streams;
47 } ConcatFile;
48
49 typedef struct {
50     AVClass *class;
51     ConcatFile *files;
52     ConcatFile *cur_file;
53     unsigned nb_files;
54     AVFormatContext *avf;
55     int safe;
56     int seekable;
57     ConcatMatchMode stream_match_mode;
58     unsigned auto_convert;
59 } ConcatContext;
60
61 static int concat_probe(AVProbeData *probe)
62 {
63     return memcmp(probe->buf, "ffconcat version 1.0", 20) ?
64            0 : AVPROBE_SCORE_MAX;
65 }
66
67 static char *get_keyword(uint8_t **cursor)
68 {
69     char *ret = *cursor += strspn(*cursor, SPACE_CHARS);
70     *cursor += strcspn(*cursor, SPACE_CHARS);
71     if (**cursor) {
72         *((*cursor)++) = 0;
73         *cursor += strspn(*cursor, SPACE_CHARS);
74     }
75     return ret;
76 }
77
78 static int safe_filename(const char *f)
79 {
80     const char *start = f;
81
82     for (; *f; f++) {
83         /* A-Za-z0-9_- */
84         if (!((unsigned)((*f | 32) - 'a') < 26 ||
85               (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) {
86             if (f == start)
87                 return 0;
88             else if (*f == '/')
89                 start = f + 1;
90             else if (*f != '.')
91                 return 0;
92         }
93     }
94     return 1;
95 }
96
97 #define FAIL(retcode) do { ret = (retcode); goto fail; } while(0)
98
99 static int add_file(AVFormatContext *avf, char *filename, ConcatFile **rfile,
100                     unsigned *nb_files_alloc)
101 {
102     ConcatContext *cat = avf->priv_data;
103     ConcatFile *file;
104     char *url = NULL;
105     const char *proto;
106     size_t url_len, proto_len;
107     int ret;
108
109     if (cat->safe > 0 && !safe_filename(filename)) {
110         av_log(avf, AV_LOG_ERROR, "Unsafe file name '%s'\n", filename);
111         FAIL(AVERROR(EPERM));
112     }
113
114     proto = avio_find_protocol_name(filename);
115     proto_len = proto ? strlen(proto) : 0;
116     if (!memcmp(filename, proto, proto_len) &&
117         (filename[proto_len] == ':' || filename[proto_len] == ',')) {
118         url = filename;
119         filename = NULL;
120     } else {
121         url_len = strlen(avf->filename) + strlen(filename) + 16;
122         if (!(url = av_malloc(url_len)))
123             FAIL(AVERROR(ENOMEM));
124         ff_make_absolute_url(url, url_len, avf->filename, filename);
125         av_freep(&filename);
126     }
127
128     if (cat->nb_files >= *nb_files_alloc) {
129         size_t n = FFMAX(*nb_files_alloc * 2, 16);
130         ConcatFile *new_files;
131         if (n <= cat->nb_files || n > SIZE_MAX / sizeof(*cat->files) ||
132             !(new_files = av_realloc(cat->files, n * sizeof(*cat->files))))
133             FAIL(AVERROR(ENOMEM));
134         cat->files = new_files;
135         *nb_files_alloc = n;
136     }
137
138     file = &cat->files[cat->nb_files++];
139     memset(file, 0, sizeof(*file));
140     *rfile = file;
141
142     file->url        = url;
143     file->start_time = AV_NOPTS_VALUE;
144     file->duration   = AV_NOPTS_VALUE;
145
146     return 0;
147
148 fail:
149     av_free(url);
150     av_free(filename);
151     return ret;
152 }
153
154 static int copy_stream_props(AVStream *st, AVStream *source_st)
155 {
156     int ret;
157
158     if (st->codec->codec_id || !source_st->codec->codec_id) {
159         if (st->codec->extradata_size < source_st->codec->extradata_size) {
160             ret = ff_alloc_extradata(st->codec,
161                                      source_st->codec->extradata_size);
162             if (ret < 0)
163                 return ret;
164         }
165         memcpy(st->codec->extradata, source_st->codec->extradata,
166                source_st->codec->extradata_size);
167         return 0;
168     }
169     if ((ret = avcodec_copy_context(st->codec, source_st->codec)) < 0)
170         return ret;
171     st->r_frame_rate        = source_st->r_frame_rate;
172     st->avg_frame_rate      = source_st->avg_frame_rate;
173     st->time_base           = source_st->time_base;
174     st->sample_aspect_ratio = source_st->sample_aspect_ratio;
175
176     av_dict_copy(&st->metadata, source_st->metadata, 0);
177     return 0;
178 }
179
180 static int detect_stream_specific(AVFormatContext *avf, int idx)
181 {
182     ConcatContext *cat = avf->priv_data;
183     AVStream *st = cat->avf->streams[idx];
184     ConcatStream *cs = &cat->cur_file->streams[idx];
185     AVBitStreamFilterContext *bsf;
186
187     if (cat->auto_convert && st->codec->codec_id == AV_CODEC_ID_H264 &&
188         (st->codec->extradata_size < 4 || AV_RB32(st->codec->extradata) != 1)) {
189         av_log(cat->avf, AV_LOG_INFO,
190                "Auto-inserting h264_mp4toannexb bitstream filter\n");
191         if (!(bsf = av_bitstream_filter_init("h264_mp4toannexb"))) {
192             av_log(avf, AV_LOG_ERROR, "h264_mp4toannexb bitstream filter "
193                    "required for H.264 streams\n");
194             return AVERROR_BSF_NOT_FOUND;
195         }
196         cs->bsf = bsf;
197     }
198     return 0;
199 }
200
201 static int match_streams_one_to_one(AVFormatContext *avf)
202 {
203     ConcatContext *cat = avf->priv_data;
204     AVStream *st;
205     int i, ret;
206
207     for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) {
208         if (i < avf->nb_streams) {
209             st = avf->streams[i];
210         } else {
211             if (!(st = avformat_new_stream(avf, NULL)))
212                 return AVERROR(ENOMEM);
213         }
214         if ((ret = copy_stream_props(st, cat->avf->streams[i])) < 0)
215             return ret;
216         cat->cur_file->streams[i].out_stream_index = i;
217     }
218     return 0;
219 }
220
221 static int match_streams_exact_id(AVFormatContext *avf)
222 {
223     ConcatContext *cat = avf->priv_data;
224     AVStream *st;
225     int i, j, ret;
226
227     for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) {
228         st = cat->avf->streams[i];
229         for (j = 0; j < avf->nb_streams; j++) {
230             if (avf->streams[j]->id == st->id) {
231                 av_log(avf, AV_LOG_VERBOSE,
232                        "Match slave stream #%d with stream #%d id 0x%x\n",
233                        i, j, st->id);
234                 if ((ret = copy_stream_props(avf->streams[j], st)) < 0)
235                     return ret;
236                 cat->cur_file->streams[i].out_stream_index = j;
237             }
238         }
239     }
240     return 0;
241 }
242
243 static int match_streams(AVFormatContext *avf)
244 {
245     ConcatContext *cat = avf->priv_data;
246     ConcatStream *map;
247     int i, ret;
248
249     if (cat->cur_file->nb_streams >= cat->avf->nb_streams)
250         return 0;
251     map = av_realloc(cat->cur_file->streams,
252                      cat->avf->nb_streams * sizeof(*map));
253     if (!map)
254         return AVERROR(ENOMEM);
255     cat->cur_file->streams = map;
256     memset(map + cat->cur_file->nb_streams, 0,
257            (cat->avf->nb_streams - cat->cur_file->nb_streams) * sizeof(*map));
258
259     for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++)
260         map[i].out_stream_index = -1;
261     switch (cat->stream_match_mode) {
262     case MATCH_ONE_TO_ONE:
263         ret = match_streams_one_to_one(avf);
264         break;
265     case MATCH_EXACT_ID:
266         ret = match_streams_exact_id(avf);
267         break;
268     default:
269         ret = AVERROR_BUG;
270     }
271     if (ret < 0)
272         return ret;
273     for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++)
274         if ((ret = detect_stream_specific(avf, i)) < 0)
275             return ret;
276     cat->cur_file->nb_streams = cat->avf->nb_streams;
277     return 0;
278 }
279
280 static int open_file(AVFormatContext *avf, unsigned fileno)
281 {
282     ConcatContext *cat = avf->priv_data;
283     ConcatFile *file = &cat->files[fileno];
284     int ret;
285
286     if (cat->avf)
287         avformat_close_input(&cat->avf);
288
289     cat->avf = avformat_alloc_context();
290     if (!cat->avf)
291         return AVERROR(ENOMEM);
292
293     cat->avf->interrupt_callback = avf->interrupt_callback;
294
295     if ((ret = ff_copy_whitelists(cat->avf, avf)) < 0)
296         return ret;
297
298     if ((ret = avformat_open_input(&cat->avf, file->url, NULL, NULL)) < 0 ||
299         (ret = avformat_find_stream_info(cat->avf, NULL)) < 0) {
300         av_log(avf, AV_LOG_ERROR, "Impossible to open '%s'\n", file->url);
301         avformat_close_input(&cat->avf);
302         return ret;
303     }
304     cat->cur_file = file;
305     if (file->start_time == AV_NOPTS_VALUE)
306         file->start_time = !fileno ? 0 :
307                            cat->files[fileno - 1].start_time +
308                            cat->files[fileno - 1].duration;
309     if ((ret = match_streams(avf)) < 0)
310         return ret;
311     return 0;
312 }
313
314 static int concat_read_close(AVFormatContext *avf)
315 {
316     ConcatContext *cat = avf->priv_data;
317     unsigned i;
318
319     if (cat->avf)
320         avformat_close_input(&cat->avf);
321     for (i = 0; i < cat->nb_files; i++) {
322         av_freep(&cat->files[i].url);
323         av_freep(&cat->files[i].streams);
324     }
325     av_freep(&cat->files);
326     return 0;
327 }
328
329 static int concat_read_header(AVFormatContext *avf)
330 {
331     ConcatContext *cat = avf->priv_data;
332     uint8_t buf[4096];
333     uint8_t *cursor, *keyword;
334     int ret, line = 0, i;
335     unsigned nb_files_alloc = 0;
336     ConcatFile *file = NULL;
337     int64_t time = 0;
338
339     while (1) {
340         if ((ret = ff_get_line(avf->pb, buf, sizeof(buf))) <= 0)
341             break;
342         line++;
343         cursor = buf;
344         keyword = get_keyword(&cursor);
345         if (!*keyword || *keyword == '#')
346             continue;
347
348         if (!strcmp(keyword, "file")) {
349             char *filename = av_get_token((const char **)&cursor, SPACE_CHARS);
350             if (!filename) {
351                 av_log(avf, AV_LOG_ERROR, "Line %d: filename required\n", line);
352                 FAIL(AVERROR_INVALIDDATA);
353             }
354             if ((ret = add_file(avf, filename, &file, &nb_files_alloc)) < 0)
355                 goto fail;
356         } else if (!strcmp(keyword, "duration")) {
357             char *dur_str = get_keyword(&cursor);
358             int64_t dur;
359             if (!file) {
360                 av_log(avf, AV_LOG_ERROR, "Line %d: duration without file\n",
361                        line);
362                 FAIL(AVERROR_INVALIDDATA);
363             }
364             if ((ret = av_parse_time(&dur, dur_str, 1)) < 0) {
365                 av_log(avf, AV_LOG_ERROR, "Line %d: invalid duration '%s'\n",
366                        line, dur_str);
367                 goto fail;
368             }
369             file->duration = dur;
370         } else if (!strcmp(keyword, "stream")) {
371             if (!avformat_new_stream(avf, NULL))
372                 FAIL(AVERROR(ENOMEM));
373         } else if (!strcmp(keyword, "exact_stream_id")) {
374             if (!avf->nb_streams) {
375                 av_log(avf, AV_LOG_ERROR, "Line %d: exact_stream_id without stream\n",
376                        line);
377                 FAIL(AVERROR_INVALIDDATA);
378             }
379             avf->streams[avf->nb_streams - 1]->id =
380                 strtol(get_keyword(&cursor), NULL, 0);
381         } else if (!strcmp(keyword, "ffconcat")) {
382             char *ver_kw  = get_keyword(&cursor);
383             char *ver_val = get_keyword(&cursor);
384             if (strcmp(ver_kw, "version") || strcmp(ver_val, "1.0")) {
385                 av_log(avf, AV_LOG_ERROR, "Line %d: invalid version\n", line);
386                 FAIL(AVERROR_INVALIDDATA);
387             }
388             if (cat->safe < 0)
389                 cat->safe = 1;
390         } else {
391             av_log(avf, AV_LOG_ERROR, "Line %d: unknown keyword '%s'\n",
392                    line, keyword);
393             FAIL(AVERROR_INVALIDDATA);
394         }
395     }
396     if (ret < 0)
397         goto fail;
398     if (!cat->nb_files)
399         FAIL(AVERROR_INVALIDDATA);
400
401     for (i = 0; i < cat->nb_files; i++) {
402         if (cat->files[i].start_time == AV_NOPTS_VALUE)
403             cat->files[i].start_time = time;
404         else
405             time = cat->files[i].start_time;
406         if (cat->files[i].duration == AV_NOPTS_VALUE)
407             break;
408         time += cat->files[i].duration;
409     }
410     if (i == cat->nb_files) {
411         avf->duration = time;
412         cat->seekable = 1;
413     }
414
415     cat->stream_match_mode = avf->nb_streams ? MATCH_EXACT_ID :
416                                                MATCH_ONE_TO_ONE;
417     if ((ret = open_file(avf, 0)) < 0)
418         goto fail;
419     return 0;
420
421 fail:
422     concat_read_close(avf);
423     return ret;
424 }
425
426 static int open_next_file(AVFormatContext *avf)
427 {
428     ConcatContext *cat = avf->priv_data;
429     unsigned fileno = cat->cur_file - cat->files;
430
431     if (cat->cur_file->duration == AV_NOPTS_VALUE)
432         cat->cur_file->duration = cat->avf->duration;
433
434     if (++fileno >= cat->nb_files)
435         return AVERROR_EOF;
436     return open_file(avf, fileno);
437 }
438
439 static int filter_packet(AVFormatContext *avf, ConcatStream *cs, AVPacket *pkt)
440 {
441     AVStream *st = avf->streams[cs->out_stream_index];
442     AVBitStreamFilterContext *bsf;
443     AVPacket pkt2;
444     int ret;
445
446     av_assert0(cs->out_stream_index >= 0);
447     for (bsf = cs->bsf; bsf; bsf = bsf->next) {
448         pkt2 = *pkt;
449         ret = av_bitstream_filter_filter(bsf, st->codec, NULL,
450                                          &pkt2.data, &pkt2.size,
451                                          pkt->data, pkt->size,
452                                          !!(pkt->flags & AV_PKT_FLAG_KEY));
453         if (ret < 0) {
454             av_packet_unref(pkt);
455             return ret;
456         }
457         av_assert0(pkt2.buf);
458         if (ret == 0 && pkt2.data != pkt->data) {
459             if ((ret = av_copy_packet(&pkt2, pkt)) < 0) {
460                 av_free(pkt2.data);
461                 return ret;
462             }
463             ret = 1;
464         }
465         if (ret > 0) {
466             av_free_packet(pkt);
467             pkt2.buf = av_buffer_create(pkt2.data, pkt2.size,
468                                         av_buffer_default_free, NULL, 0);
469             if (!pkt2.buf) {
470                 av_free(pkt2.data);
471                 return AVERROR(ENOMEM);
472             }
473         }
474         *pkt = pkt2;
475     }
476     return 0;
477 }
478
479 static int concat_read_packet(AVFormatContext *avf, AVPacket *pkt)
480 {
481     ConcatContext *cat = avf->priv_data;
482     int ret;
483     int64_t file_start_time, delta;
484     ConcatStream *cs;
485     AVStream *st;
486
487     if (!cat->avf)
488         return AVERROR(EIO);
489
490     while (1) {
491         ret = av_read_frame(cat->avf, pkt);
492         if (ret == AVERROR_EOF) {
493             if ((ret = open_next_file(avf)) < 0)
494                 return ret;
495             continue;
496         }
497         if (ret < 0)
498             return ret;
499         if ((ret = match_streams(avf)) < 0) {
500             av_packet_unref(pkt);
501             return ret;
502         }
503         cs = &cat->cur_file->streams[pkt->stream_index];
504         if (cs->out_stream_index < 0) {
505             av_packet_unref(pkt);
506             continue;
507         }
508         pkt->stream_index = cs->out_stream_index;
509         break;
510     }
511     if ((ret = filter_packet(avf, cs, pkt)))
512         return ret;
513
514     st = cat->avf->streams[pkt->stream_index];
515     av_log(avf, AV_LOG_DEBUG, "file:%d stream:%d pts:%s pts_time:%s dts:%s dts_time:%s",
516            (unsigned)(cat->cur_file - cat->files), pkt->stream_index,
517            av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),
518            av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));
519
520     file_start_time = cat->avf->start_time;
521     if (file_start_time == AV_NOPTS_VALUE)
522         file_start_time = 0;
523     delta = av_rescale_q(cat->cur_file->start_time - file_start_time,
524                          AV_TIME_BASE_Q,
525                          cat->avf->streams[pkt->stream_index]->time_base);
526     if (pkt->pts != AV_NOPTS_VALUE)
527         pkt->pts += delta;
528     if (pkt->dts != AV_NOPTS_VALUE)
529         pkt->dts += delta;
530     av_log(avf, AV_LOG_DEBUG, " -> pts:%s pts_time:%s dts:%s dts_time:%s\n",
531            av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),
532            av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));
533     return ret;
534 }
535
536 static void rescale_interval(AVRational tb_in, AVRational tb_out,
537                              int64_t *min_ts, int64_t *ts, int64_t *max_ts)
538 {
539     *ts     = av_rescale_q    (*    ts, tb_in, tb_out);
540     *min_ts = av_rescale_q_rnd(*min_ts, tb_in, tb_out,
541                                AV_ROUND_UP   | AV_ROUND_PASS_MINMAX);
542     *max_ts = av_rescale_q_rnd(*max_ts, tb_in, tb_out,
543                                AV_ROUND_DOWN | AV_ROUND_PASS_MINMAX);
544 }
545
546 static int try_seek(AVFormatContext *avf, int stream,
547                     int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
548 {
549     ConcatContext *cat = avf->priv_data;
550     int64_t t0 = cat->cur_file->start_time - cat->avf->start_time;
551
552     ts -= t0;
553     min_ts = min_ts == INT64_MIN ? INT64_MIN : min_ts - t0;
554     max_ts = max_ts == INT64_MAX ? INT64_MAX : max_ts - t0;
555     if (stream >= 0) {
556         if (stream >= cat->avf->nb_streams)
557             return AVERROR(EIO);
558         rescale_interval(AV_TIME_BASE_Q, cat->avf->streams[stream]->time_base,
559                          &min_ts, &ts, &max_ts);
560     }
561     return avformat_seek_file(cat->avf, stream, min_ts, ts, max_ts, flags);
562 }
563
564 static int real_seek(AVFormatContext *avf, int stream,
565                      int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
566 {
567     ConcatContext *cat = avf->priv_data;
568     int ret, left, right;
569
570     if (stream >= 0) {
571         if (stream >= avf->nb_streams)
572             return AVERROR(EINVAL);
573         rescale_interval(avf->streams[stream]->time_base, AV_TIME_BASE_Q,
574                          &min_ts, &ts, &max_ts);
575     }
576
577     left  = 0;
578     right = cat->nb_files;
579     while (right - left > 1) {
580         int mid = (left + right) / 2;
581         if (ts < cat->files[mid].start_time)
582             right = mid;
583         else
584             left  = mid;
585     }
586
587     if ((ret = open_file(avf, left)) < 0)
588         return ret;
589
590     ret = try_seek(avf, stream, min_ts, ts, max_ts, flags);
591     if (ret < 0 &&
592         left < cat->nb_files - 1 &&
593         cat->files[left + 1].start_time < max_ts) {
594         if ((ret = open_file(avf, left + 1)) < 0)
595             return ret;
596         ret = try_seek(avf, stream, min_ts, ts, max_ts, flags);
597     }
598     return ret;
599 }
600
601 static int concat_seek(AVFormatContext *avf, int stream,
602                        int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
603 {
604     ConcatContext *cat = avf->priv_data;
605     ConcatFile *cur_file_saved = cat->cur_file;
606     AVFormatContext *cur_avf_saved = cat->avf;
607     int ret;
608
609     if (!cat->seekable)
610         return AVERROR(ESPIPE); /* XXX: can we use it? */
611     if (flags & (AVSEEK_FLAG_BYTE | AVSEEK_FLAG_FRAME))
612         return AVERROR(ENOSYS);
613     cat->avf = NULL;
614     if ((ret = real_seek(avf, stream, min_ts, ts, max_ts, flags)) < 0) {
615         if (cat->avf)
616             avformat_close_input(&cat->avf);
617         cat->avf      = cur_avf_saved;
618         cat->cur_file = cur_file_saved;
619     } else {
620         avformat_close_input(&cur_avf_saved);
621     }
622     return ret;
623 }
624
625 #define OFFSET(x) offsetof(ConcatContext, x)
626 #define DEC AV_OPT_FLAG_DECODING_PARAM
627
628 static const AVOption options[] = {
629     { "safe", "enable safe mode",
630       OFFSET(safe), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 1, DEC },
631     { "auto_convert", "automatically convert bitstream format",
632       OFFSET(auto_convert), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, DEC },
633     { NULL }
634 };
635
636 static const AVClass concat_class = {
637     .class_name = "concat demuxer",
638     .item_name  = av_default_item_name,
639     .option     = options,
640     .version    = LIBAVUTIL_VERSION_INT,
641 };
642
643
644 AVInputFormat ff_concat_demuxer = {
645     .name           = "concat",
646     .long_name      = NULL_IF_CONFIG_SMALL("Virtual concatenation script"),
647     .priv_data_size = sizeof(ConcatContext),
648     .read_probe     = concat_probe,
649     .read_header    = concat_read_header,
650     .read_packet    = concat_read_packet,
651     .read_close     = concat_read_close,
652     .read_seek2     = concat_seek,
653     .priv_class     = &concat_class,
654 };